Puoi implementarlo senza trucchi sporchi . Basta estendere la chiave esterna facendo riferimento all'opzione scelta per includere variable_id
oltre a choice_id
.
Ecco una demo funzionante. Tavoli temporanei, così puoi giocarci facilmente:
CREATE TABLE systemvariables (
variable_id int PRIMARY KEY
, choice_id int
, variable text
);
INSERT INTO systemvariables(variable_id, variable) VALUES
(1, 'var1')
, (2, 'var2')
, (3, 'var3')
;
CREATE TABLE variableoptions (
option_id int PRIMARY KEY
, variable_id int REFERENCES systemvariables ON UPDATE CASCADE ON DELETE CASCADE
, option text
, UNIQUE (option_id, variable_id) -- needed for the FK
);
ALTER TABLE systemvariables
ADD CONSTRAINT systemvariables_choice_id_fk
FOREIGN KEY (choice_id, variable_id) REFERENCES variableoptions(option_id, variable_id);
INSERT INTO variableoptions VALUES
(1, 'var1_op1', 1)
, (2, 'var1_op2', 1)
, (3, 'var1_op3', 1)
, (4, 'var2_op1', 2)
, (5, 'var2_op2', 2)
, (6, 'var3_op1', 3)
;
È consentita la scelta di un'opzione associata:
UPDATE systemvariables SET choice_id = 2 WHERE variable_id = 1;
UPDATE systemvariables SET choice_id = 5 WHERE variable_id = 2;
UPDATE systemvariables SET choice_id = 6 WHERE variable_id = 3;
Ma non c'è modo di uscire dalla linea:
UPDATE systemvariables SET choice_id = 7 WHERE variable_id = 3;
UPDATE systemvariables SET choice_id = 4 WHERE variable_id = 1;
ERROR: insert or update on table "systemvariables" violates foreign key constraint "systemvariables_choice_id_fk" DETAIL: Key (choice_id,variable_id)=(4,1) is not present in table "variableoptions".
Esattamente quello che volevi.
Tutte le colonne chiave NOT NULL
Penso di aver trovato una soluzione migliore in questa risposta successiva:
- Come gestire gli inserti reciprocamente dipendenti
Rispondendo alla domanda di @ypercube nei commenti, per evitare voci con associazione sconosciuta, rendere tutte le colonne chiave NOT NULL
, comprese le chiavi esterne.
La dipendenza circolare normalmente lo renderebbe impossibile. È il classico uovo di gallina problema:uno di entrambi deve essere lì prima per generare l'altro. Ma la natura ha trovato un modo per aggirarlo, e così ha fatto Postgres:vincoli di chiave esterna differibili .
CREATE TABLE systemvariables (
variable_id int PRIMARY KEY
, variable text
, choice_id int NOT NULL
);
CREATE TABLE variableoptions (
option_id int PRIMARY KEY
, option text
, variable_id int NOT NULL REFERENCES systemvariables
ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
, UNIQUE (option_id, variable_id) -- needed for the foreign key
);
ALTER TABLE systemvariables
ADD CONSTRAINT systemvariables_choice_id_fk FOREIGN KEY (choice_id, variable_id)
REFERENCES variableoptions(option_id, variable_id) DEFERRABLE INITIALLY DEFERRED; -- no CASCADING here!
Nuovo le variabili e le opzioni associate devono essere inserite nella stessa transazione:
BEGIN;
INSERT INTO systemvariables (variable_id, variable, choice_id)
VALUES
(1, 'var1', 2)
, (2, 'var2', 5)
, (3, 'var3', 6);
INSERT INTO variableoptions (option_id, option, variable_id)
VALUES
(1, 'var1_op1', 1)
, (2, 'var1_op2', 1)
, (3, 'var1_op3', 1)
, (4, 'var2_op1', 2)
, (5, 'var2_op2', 2)
, (6, 'var3_op1', 3);
END;
Il NOT NULL
il vincolo non può essere differito, viene applicato immediatamente. Ma il vincolo della chiave esterna può , perché l'abbiamo definito così. Viene verificato al termine della transazione, il che evita il problema dell'uovo di gallina.
In questo modificato scenario, entrambe le chiavi esterne sono differite . Puoi inserire variabili e opzioni in sequenza arbitraria.
Puoi anche farlo funzionare con un semplice vincolo FK non differibile se inserisci voci correlate in entrambe le tabelle in un'istruzione utilizzando CTE come dettagliato nella risposta collegata.
Potresti aver notato che il primo vincolo di chiave esterna non ha CASCADE
modificatore. (Non avrebbe senso consentire modifiche a variableoptions.variable_id
a cascata indietro.
D'altra parte, la seconda chiave esterna ha un CASCADE
modificatore ed è definito DEFERRABLE
ciò nonostante. Questo comporta alcune limitazioni. Il manuale:
Azioni referenziali diverse da NO ACTION
la verifica non può essere differita,anche se il vincolo è dichiarato differibile.
NO ACTION
è l'impostazione predefinita.
Quindi, l'integrità referenziale verifica su INSERT
sono posticipati, ma le azioni a cascata dichiarate su DELETE
e UPDATE
non sono. Quanto segue non è consentito in PostgreSQL 9.0 o versioni successive perché i vincoli vengono applicati dopo ogni istruzione:
UPDATE option SET var_id = 4 WHERE var_id = 5;
DELETE FROM var WHERE var_id = 5;
Dettagli:
- Il vincolo definito DIFFERIBILE INIZIAMENTE IMMEDIATO è ancora RINVIATO?