sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo tener una clave externa que apunte a dos claves principales?

Reglas para restricciones FK

Para responder a la pregunta en el título y al final de su texto:

"Todavía me gustaría saber cómo hacer que una clave externa haga referencia a dos claves principales".

Eso es imposible.

  • Una FOREIGN KEY la restricción solo puede apuntar a uno tabla y cada tabla solo puede tener una PRIMARY KEY restricción.

  • O puede tener múltiples FOREIGN KEY restricciones en la(s) misma(s) columna(s) que hacen referencia a uno PRIMARY KEY de una mesa (diferente) cada uno. (Rara vez útil.)

Sin embargo , un solo PK o FK puede abarcar varias columnas.
Y un FK puede hacer referencia a cualquier (conjunto de) columna(s) única(s) explícitamente definida(s) en el destino, no solo al PK. El manual:

Un PK de varias columnas o UNIQUE la restricción solo puede ser referenciada por una restricción FK de varias columnas con tipos de columna coincidentes.

Lo que pides

Dado que no está permitido usar la misma columna más de una vez en la lista de columnas de un UNIQUE o PRIMARY KEY restricción, la lista de destino de una FOREIGN KEY Tampoco se puede utilizar la misma columna más de una vez. Pero no hay nada que nos impida usar la misma columna más de una vez en la fuente lista. Aquí radica el potencial para implementar lo que está preguntando (pero probablemente no fue su intención):

"En el team_statistics tabla el team_statistics.team_id debe ser una clave externa que haga referencia a matches.team_id y matches.team_id1 "

La combinación de (team_id, team_id1) en la tabla matches necesitaría ser definido UNIQUE . Valores en team_statistics.team_id estaría restringido a casos con team = team1 en la tabla matches como consecuencia lógica:

ALTER TABLE matches
ADD constraint matches_teams_groups_uni UNIQUE (team_id, team_id1);

ALTER TABLE team_statistics
  ADD constraint team_statistics_team_group fkey
  FOREIGN KEY (team_id, team_id)  -- same column twice!
  REFERENCES matches(team_id, team_id1);

Incluso podría tener sentido para ciertas configuraciones, pero no para la tuya.

Lo que probablemente necesites

Supongo que quieres algo como esto:

(match_id, team_id) en la tabla team_statistics debe ser una clave externa que haga referencia a cualquiera (match_id, team_id) o (match_id, team_id1) en la tabla matches .

Y eso no es posible con restricciones FK y solo dos tablas. podrías abusar de un CHECK restricción con un falso IMMUTABLE función y convertirlo en NOT VALID . Consulte el capítulo "Más barato con una restricción CHECK" en esta respuesta:

Pero eso es un engaño avanzado y menos confiable. No es mi sugerencia aquí, así que no voy a dar más detalles. Sugiero normalizar su esquema de una manera útil, como:

CREATE TABLE team (team_id serial PRIMARY KEY
                 , team text NOT NULL UNIQUE);     -- add more attributes for team

CREATE TABLE match (match_id serial PRIMARY KEY);  -- add more attributes for match

CREATE TABLE match_team (
   match_id  int  REFERENCES match  -- short notation for FK
 , team_id   int  REFERENCES team
 , home boolean                     -- TRUE for home team, FALSE for away team
 , innings_score int
 -- more attributes of your original "team_statistics"
 , PRIMARY KEY (match_id, team_id, home)  -- !!! (1st column = match_id)
 , UNIQUE (team_id, match_id)             -- optional, (1st column = team_id)
);

home marca el equipo local del partido pero, por inclusión en el PK, también restringe a máximo dos equipos por partido . (Las columnas PK están definidas NOT NULL implícitamente.)

El UNIQUE opcional restricción en (team_id, match_id) evita que los equipos jueguen contra ellos mismos. Al usar la secuencia invertida de columnas de índice (irrelevante para hacer cumplir la regla), esto también proporciona un índice complementario al PK, que normalmente también es útil. Ver:

podrías agregue un match_team_statistics separado , pero eso solo sería una extensión 1:1 opcional para match_team ahora. Alternativamente, simplemente agregue columnas a match_team .

Podría agregar vistas para pantallas típicas, como:

CREATE VIEW match_result AS
SELECT m.match_id
     , concat_ws(' : ', t1.team, t2.team) AS home_vs_away_team
     , concat_ws(' : ', mt1.innings_score, mt2.innings_score) AS result
FROM   match           m
LEFT   JOIN match_team mt1 ON mt1.match_id = m.match_id AND mt1.home
LEFT   JOIN team       t1  ON t1.team_id = mt1.team_id
LEFT   JOIN match_team mt2 ON mt2.match_id = m.match_id AND NOT mt2.home
LEFT   JOIN team       t2  ON t2.team_id = mt2.team_id;

Consejo básico: