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 unoPRIMARY KEY
vincolo. -
Oppure puoi averne più
FOREIGN KEY
vincoli sulle stesse colonne che fanno riferimento a unoPRIMARY 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: