sql >> Base de Datos >  >> RDS >> PostgreSQL

Restricción de clave externa compleja en SQLAlchemy

Puedes implementar eso sin trucos sucios . Simplemente extienda la clave externa haciendo referencia a la opción elegida para incluir variable_id además de choice_id .

Aquí hay una demostración de trabajo. Mesas temporales, para que puedas jugar fácilmente con ellas:

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

Se permite elegir una opción asociada:

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;

Pero no se puede pasar de la raya:

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

Exactamente lo que querías.

Todas las columnas clave NOT NULL

Creo que encontré una mejor solución en esta respuesta posterior:

  • Cómo lidiar con inserciones mutuamente dependientes

Al abordar la pregunta de @ypercube en los comentarios, para evitar entradas con asociación desconocida, haga que todas las columnas clave NOT NULL , incluidas las claves externas.

La dependencia circular normalmente lo haría imposible. Es el clásico huevo de gallina problema:uno de los dos tiene que estar allí primero para engendrar al otro. Pero la naturaleza encontró una forma de evitarlo, y también lo hizo Postgres:restricciones de clave externa diferibles .

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!

Nuevo las variables y las opciones asociadas deben insertarse en la misma transacción:

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;

El NOT NULL la restricción no se puede aplazar, se aplica de inmediato. Pero la restricción de clave externa puede , porque así lo definimos. Se comprueba al final de la transacción, lo que evita el problema del huevo de gallina.

En este editado escenario, ambas claves foráneas son diferidas . Puede ingresar variables y opciones en secuencia arbitraria.
Incluso puede hacer que funcione con una restricción FK simple no diferible si ingresa entradas relacionadas en ambas tablas en una declaración usando CTE como se detalla en la respuesta vinculada.

Es posible que haya notado que la primera restricción de clave externa no tiene CASCADE modificador (No tendría sentido permitir cambios en variableoptions.variable_id para regresar en cascada.

Por otro lado, la segunda clave foránea tiene una CASCADE modificador y se define DEFERRABLE sin embargo. Esto conlleva algunas limitaciones. El manual:

Acciones referenciales que no sean NO ACTION el cheque no se puede aplazar, incluso si la restricción se declara aplazable.

NO ACTION es el predeterminado.

Entonces, controles de integridad referencial en INSERT se difieren, pero las acciones en cascada declaradas en DELETE y UPDATE no son. Lo siguiente no está permitido en PostgreSQL 9.0 o posterior porque se imponen restricciones después de cada instrucción:

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

Detalles:

  • ¿La restricción definida APLAZABLE INICIALMENTE INMEDIATO sigue APLAZADA?