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

Vincolo di chiave esterna complesso in SQLAlchemy

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?