PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Come avere una chiave esterna che punta a due chiavi primarie?

Regole per i vincoli FK

Per rispondere alla domanda nel titolo e alla fine del testo:

"Vorrei ancora sapere come avere una chiave esterna che faccia riferimento a due chiavi primarie."

È impossibile.

  • Un FOREIGN KEY il vincolo può puntare solo a uno tavolo e ogni tavolo può averne solo uno PRIMARY KEY vincolo.

  • Oppure puoi averne più FOREIGN KEY vincoli sulle stesse colonne che fanno riferimento a uno PRIMARY KEY di una (diversa) tabella ciascuno. (Raramente utile.)

Tuttavia , un singolo PK o FK può estende su più colonne.
E un FK può fare riferimento a qualsiasi colonna (set di) univoca definita in modo esplicito nella destinazione, non solo al PK. Il manuale:

Un PK multicolonna o UNIQUE il vincolo può essere referenziato solo da un vincolo FK multicolonna con tipi di colonna corrispondenti.

Cosa chiedi

Poiché non è consentito utilizzare la stessa colonna più di una volta nell'elenco delle colonne di un UNIQUE o PRIMARY KEY vincolo, l'elenco di destinazione di una FOREIGN KEY inoltre non può utilizzare la stessa colonna più di una volta. Ma nulla ci impedisce di utilizzare la stessa colonna più di una volta nella fonte elenco. Qui sta il potenziale per implementare ciò che stai chiedendo (ma probabilmente non intendevi farlo):

"Nel team_statistics tabella il team_statistics.team_id dovrebbe essere una chiave esterna che fa riferimento a matches.team_id e matches.team_id1 "

La combinazione di (team_id, team_id1) nella tabella matches dovrebbe essere definito UNIQUE . Valori in team_statistics.team_id sarebbe limitato ai casi con team = team1 nella tabella matches come logica conseguenza:

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);

Potrebbe anche avere senso per alcune configurazioni, ma non per le tue.

Di cosa hai probabilmente bisogno

La mia ipotesi plausibile è che tu voglia qualcosa del genere:

(match_id, team_id) nella tabella team_statistics dovrebbe essere una chiave esterna che fa riferimento a entrambi (match_id, team_id) o (match_id, team_id1) nella tabella matches .

E questo non è possibile con i vincoli FK e solo due tabelle. Potresti abusare di un CHECK vincolo con un falso IMMUTABLE funzione e rendilo NOT VALID . Vedi il capitolo "Più economico con un vincolo CHECK" in questa risposta:

Ma questo è un trucco avanzato e meno affidabile. Non è il mio suggerimento qui, quindi non ho intenzione di approfondire. Suggerisco di normalizzare il tuo schema in modo utile, come:

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 segna la squadra di casa della partita ma, per inclusione nel PK, si limita anche a max due squadre per partita . (Le colonne PK sono definite NOT NULL implicitamente.)

Il UNIQUE facoltativo vincolo su (team_id, match_id) impedisce alle squadre di giocare contro se stesse. Utilizzando la sequenza invertita delle colonne dell'indice (irrilevante per l'applicazione della regola) ciò fornisce anche un indice complementare al PK, che in genere è anche utile. Vedi:

Potresti aggiungi una match_team_statistics separata , ma sarebbe solo un'estensione 1:1 opzionale per match_team adesso. In alternativa, aggiungi semplicemente colonne a match_team .

Potrei aggiungere viste per display tipici, come:

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;

Consiglio di base: