sql >> Databasteknik >  >> RDS >> PostgreSQL

Komplex främmande nyckel-begränsning i SQLAlchemy

Du kan implementera det utan smutsiga knep . förläng bara den främmande nyckeln hänvisar till det valda alternativet för att inkludera variable_id förutom choice_id .

Här är en fungerande demo. Tillfälliga bord, så att du enkelt kan spela med det:

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

Att välja ett associerat alternativ är tillåtet:

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;

Men det går inte att komma ur linjen:

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".

Precis vad du ville ha.

Alla nyckelkolumner NOT NULL

Jag tror att jag hittade en bättre lösning i detta senare svar:

  • Hur man hanterar ömsesidigt beroende skär

Att adressera @ypercubes fråga i kommentarerna, för att undvika poster med okänd association gör alla nyckelkolumner NOT NULL , inklusive främmande nycklar.

Det cirkulära beroendet skulle normalt göra det omöjligt. Det är det klassiska kycklingägget problem:en av båda måste vara där först för att skapa den andra. Men naturen hittade en väg runt det, och det gjorde Postgres också:förskjutbara främmande nyckelbegränsningar .

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!

Ny variabler och associerade alternativ måste infogas i samma transaktion:

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;

NOT NULL begränsning kan inte skjutas upp, den verkställs omedelbart. Men den främmande nyckeln kan , eftersom vi definierade det så. Det kontrolleras i slutet av transaktionen, vilket undviker höns-ägg-problemet.

I denna redigerade scenario, båda främmande nycklarna skjuts upp . Du kan ange variabler och alternativ i godtycklig ordning.
Du kan till och med få det att fungera med en vanlig icke-uppskjutbar FK-begränsning om du anger relaterade poster i båda tabellerna i ett uttalande använder CTE enligt beskrivningen i det länkade svaret.

Du kanske har märkt att den första främmande nyckelbegränsningen inte har någon CASCADE modifierare. (Det vore inte vettigt att tillåta ändringar av variableoptions.variable_id att kaskad tillbaka.

Å andra sidan har den andra främmande nyckeln en CASCADE modifierare och definieras som DEFERRABLE ändå. Detta har vissa begränsningar. Manualen:

Andra referensåtgärder än NO ACTION kontrollen kan inte skjutas upp, även om begränsningen förklaras uppskjuten.

NO ACTION är standard.

Så, referensintegritetskontroller på INSERT skjuts upp, men de deklarerade överlappande åtgärderna på DELETE och UPDATE är inte. Följande är inte tillåtet i PostgreSQL 9.0 eller senare eftersom begränsningar upprätthålls efter varje uttalande:

UPDATE option SET var_id = 4 WHERE var_id = 5;
DELETE FROM var WHERE var_id = 5;

Detaljer:

  • Begränsning som definierats UPPHÖJBAR INitialt OMEDELBART är fortfarande UPPSKJUT?


  1. Hur man infogar om rad inte finns (UPSERT) i MySQL

  2. Hur hittar jag dubbletter över flera kolumner?

  3. Vad är standardstorleken för en varchar2-ingång till Oracles lagrade procedur, och kan den ändras?

  4. Datumkolumnaritmetik i PostgreSQL-fråga