PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Komplexe Fremdschlüsseleinschränkung in SQLAlchemy

Das können Sie ohne schmutzige Tricks umsetzen . Erweitern Sie einfach den Fremdschlüssel Verweisen auf die gewählte Option, um variable_id einzuschließen zusätzlich zu choice_id .

Hier ist eine funktionierende Demo. Temporäre Tische, damit Sie problemlos damit spielen können:

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

Die Auswahl einer zugehörigen Option ist erlaubt:

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;

Aber es gibt kein Ausweichen:

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

Genau das, was Sie wollten.

Alle Schlüsselspalten NOT NULL

Ich glaube, ich habe in dieser späteren Antwort eine bessere Lösung gefunden:

  • Umgang mit voneinander abhängigen Beilagen

Um die Frage von @ypercube in den Kommentaren zu beantworten, machen Sie alle Schlüsselspalten NOT NULL, um Einträge mit unbekannter Assoziation zu vermeiden , einschließlich Fremdschlüssel.

Die zirkuläre Abhängigkeit würde dies normalerweise unmöglich machen. Es ist das klassische Hühner-Ei Problem:Einer von beiden muss zuerst da sein, um den anderen zu spawnen. Aber die Natur hat einen Weg gefunden, und so auch Postgres:aufschiebbare Fremdschlüsselbeschränkungen .

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!

Neu Variablen und zugehörige Optionen müssen in dieselbe Transaktion eingefügt werden:

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;

Der NOT NULL Einschränkung kann nicht aufgeschoben werden, sie wird sofort erzwungen. Aber die Fremdschlüsseleinschränkung kann , weil wir es so definiert haben. Es wird am Ende der Transaktion überprüft, wodurch das Henne-Ei-Problem vermieden wird.

In diesem bearbeitet Szenario werden beide Fremdschlüssel zurückgestellt . Sie können Variablen und Optionen in beliebiger Reihenfolge eingeben.
Sie können es sogar mit einer einfachen, nicht aufschiebbaren FK-Einschränkung zum Laufen bringen, wenn Sie verwandte Einträge in beide Tabellen in einer Anweisung eingeben Verwendung von CTEs, wie in der verknüpften Antwort beschrieben.

Sie haben vielleicht bemerkt, dass die erste Fremdschlüsseleinschränkung kein CASCADE hat Modifikator. (Es würde keinen Sinn machen, Änderungen an variableoptions.variable_id zuzulassen zurück zu kaskadieren.

Andererseits hat der zweite Fremdschlüssel einen CASCADE Modifikator und ist definiert als DEFERRABLE dennoch. Dies bringt einige Einschränkungen mit sich. Das Handbuch:

Andere referenzielle Aktionen als NO ACTION check kann nicht aufgeschoben werden, selbst wenn die Einschränkung als aufschiebbar deklariert ist.

NO ACTION ist die Standardeinstellung.

Referenzielle Integritätsprüfungen also bei INSERT werden zurückgestellt, aber die deklarierten kaskadierenden Aktionen auf DELETE und UPDATE sind nicht. Folgendes ist in PostgreSQL 9.0 oder höher nicht zulässig, da nach jeder Anweisung Einschränkungen erzwungen werden:

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

Einzelheiten:

  • Einschränkung definiert DEFERRABLE ANFÄNGLICH IMMEDIATE ist immer noch DEFERRED?