PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como ter uma chave estrangeira apontando para duas chaves primárias?

Regras para restrições FK


Para responder à pergunta no título e no final do seu texto:

"Ainda gostaria de saber como ter uma chave estrangeira referenciando duas chaves primárias."

Isso é impossível.

  • Uma FOREIGN KEY restrição só pode apontar para um tabela e cada tabela pode ter apenas uma CHAVE PRIMÁRIA restrição.

  • Ou você pode ter vários CHAVE ESTRANGEIRA restrições na(s) mesma(s) coluna(s) referenciando uma CHAVE PRIMÁRIA de uma mesa (diferente) cada. (Raramente útil.)

No entanto , um único PK ou FK pode abrange várias colunas.
E um FK pode referenciar qualquer coluna(s) exclusiva(s) definida(s) explicitamente no destino, não apenas o PK. O manual:

Um PK de várias colunas ou ÚNICO A restrição só pode ser referenciada por uma restrição FK de várias colunas com tipos de coluna correspondentes.

O que você pergunta


Como não é permitido usar a mesma coluna mais de uma vez na lista de colunas de um UNIQUE ou CHAVE PRIMÁRIA restrição, a lista de destino de uma FOREIGN KEY também não pode usar a mesma coluna mais de uma vez. Mas não há nada que nos impeça de usar a mesma coluna mais de uma vez na fonte Lista. Aqui reside o potencial de implementar o que você está pedindo (mas provavelmente não pretendia):

"Na team_statistics tabela o team_statistics.team_id deve ser uma chave estrangeira que faça referência a matches.team_id e matches.team_id1 "

A combinação de (team_id, team_id1) na tabela corresponde precisaria ser definido UNIQUE . Valores em team_statistics.team_id seria restrito a casos com team =team1 na tabela corresponde como consequência 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);

Pode até fazer sentido para certas configurações, mas não para a sua.

O que você provavelmente precisa


Meu palpite é que você quer algo assim:

(match_id, team_id) na tabela team_statistics deve ser uma chave estrangeira que faça referência a ou (match_id, team_id) ou (match_id, team_id1) na tabela corresponde .

E isso não é possível com restrições FK e apenas duas tabelas. Você poderia abusar de um CHECK restrição com um falso IMMUTABLE função e torná-lo NOT VALID . Consulte o capítulo "Mais barato com uma restrição CHECK" nesta resposta:

Mas isso é um truque avançado e menos confiável. Não é minha sugestão aqui, então não vou elaborar. Sugiro normalizar seu esquema de uma maneira ú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)
);

casa marca o time da casa da partida, mas, por inclusão no PK, também restringe a máximo de duas equipes por partida . (As colunas PK são definidas como NOT NULL implicitamente.)

O opcional UNIQUE restrição em (team_id, match_id) impede que as equipes joguem contra si mesmas. Ao usar a sequência invertida de colunas de índice (irrelevante para impor a regra), isso também fornece um índice complementar ao PK, que normalmente também é útil. Ver:

Você poderia adicione um match_team_statistics separado , mas isso seria apenas uma extensão 1:1 opcional para match_team agora. Como alternativa, basta adicionar colunas a match_team .

Posso adicionar visualizações para telas 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;

Conselho básico: