No es fácil de hacer en SQL, pero no es imposible. Si desea que esto se aplique solo a través de DDL, el DBMS debe tener DEFERRABLErestricciones implementadas . Esto podría hacerse (y puede comprobarse para que funcione en Postgres, que los ha implementado):
-- lets create first the 2 tables, A and B:
CREATE TABLE a
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT a_pk PRIMARY KEY (aid)
);
CREATE TABLE b
( bid INT NOT NULL,
aid INT NOT NULL,
CONSTRAINT b_pk PRIMARY KEY (bid)
);
-- then table R:
CREATE TABLE r
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT r_pk PRIMARY KEY (aid, bid),
CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,
CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
);
Hasta aquí está el diseño "normal", donde cada uno Apuede estar relacionado con cero, uno o muchos By cada uno Bpuede estar relacionado con cero, uno o muchos A.
La restricción de "participación total" necesita restricciones en el orden inverso (desde Ay Brespectivamente, referenciando R). Tener FOREIGN KEYrestricciones en direcciones opuestas (de X a Y y de Y a X) está formando un círculo (un problema de "huevo y gallina") y es por eso que al menos necesitamos uno de ellos DEFERRABLE. En este caso tenemos dos círculos ( A -> R -> Ay B -> R -> Bpor eso necesitamos dos restricciones diferibles:
-- then we add the 2 constraints that enforce the "total participation":
ALTER TABLE a
ADD CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
DEFERRABLE INITIALLY DEFERRED ;
ALTER TABLE b
ADD CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
DEFERRABLE INITIALLY DEFERRED ;
Entonces podemos probar que podemos insertar datos. Tenga en cuenta que INITIALLY DEFERREDno es necesario. Podríamos haber definido las restricciones como DEFERRABLE INITIALLY IMMEDIATEpero luego tendríamos que usar la SET CONSTRAINTSdeclaración para diferirlas durante la transacción. Sin embargo, en todos los casos, necesitamos insertar en las tablas en una sola transacción:
-- insert data
BEGIN TRANSACTION ;
INSERT INTO a (aid, bid)
VALUES
(1, 1), (2, 5),
(3, 7), (4, 1) ;
INSERT INTO b (aid, bid)
VALUES
(1, 1), (1, 2),
(2, 3), (2, 4),
(2, 5), (3, 6),
(3, 7) ;
INSERT INTO r (aid, bid)
VALUES
(1, 1), (1, 2),
(2, 3), (2, 4),
(2, 5), (3, 6),
(3, 7), (4, 1),
(4, 2), (4, 7) ;
END ;
Probado en SQLfiddle .
Si el DBMS no tiene DEFERRABLErestricciones, una solución es definir las columnas A (bid)y B (aid)como NULL. Los INSERTprocedimientos / declaraciones tendrán que insertarse primero en Ay B(poner nulos en bidy aidrespectivamente), luego insertar Ry luego actualizar los valores nulos anteriores a los valores no nulos relacionados de R.
Con este enfoque, el DBMS no cumplir los requisitos de DDL solo, sino todos los INSERT(e UPDATEy DELETEe MERGE) procedimiento tiene que ser considerado y ajustarse en consecuencia y los usuarios tienen que limitarse a utilizar únicamente ellos y no tienen acceso directo de escritura en las tablas.
Tener círculos en las FOREIGN KEYrestricciones no es considerado por muchos como la mejor práctica y por buenas razones, la complejidad es una de ellas. Con el segundo enfoque, por ejemplo (con columnas anulables), la actualización y eliminación de filas aún tendrá que hacerse con un código adicional, dependiendo del DBMS. En SQL Server, por ejemplo, no puede simplemente poner ON DELETE CASCADEporque las actualizaciones y eliminaciones en cascada no están permitidas cuando hay círculos FK.
Lea también las respuestas a esta pregunta relacionada:
¿Cómo tener una relación de uno a muchos con un niño privilegiado?
Otro tercer enfoque (ver mi respuesta en la pregunta mencionada anteriormente) es eliminar completamente los FK circulares. Por lo tanto, mantener la primera parte del código (con mesas A, B, Ry las claves externas sólo de R a A y B) casi intacta (en realidad, simplificándolo), que añaden otra mesa para Aalmacenar el "debe tener un" elemento relacionado de B. Entonces, la A (bid)columna se mueve a A_one (bid)Lo mismo se hace para la relación inversa de B a A:
CREATE TABLE a
( aid INT NOT NULL,
CONSTRAINT a_pk PRIMARY KEY (aid)
);
CREATE TABLE b
( bid INT NOT NULL,
CONSTRAINT b_pk PRIMARY KEY (bid)
);
-- then table R:
CREATE TABLE r
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT r_pk PRIMARY KEY (aid, bid),
CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,
CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
);
CREATE TABLE a_one
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT a_one_pk PRIMARY KEY (aid),
CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
);
CREATE TABLE b_one
( bid INT NOT NULL,
aid INT NOT NULL,
CONSTRAINT b_one_pk PRIMARY KEY (bid),
CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
);
La diferencia con respecto al primer y segundo enfoque es que no hay FK circulares, por lo que las actualizaciones y eliminaciones en cascada funcionarán bien. La aplicación de la "participación total" no es solo por DDL, como en el segundo enfoque, y debe hacerse mediante los procedimientos apropiados ( INSERT/UPDATE/DELETE/MERGE). Una pequeña diferencia con el segundo enfoque es que todas las columnas se pueden definir no anulables.
Otro cuarto enfoque (consulte la respuesta de @Aaron Bertrand en la pregunta mencionada anteriormente) es usar índices únicos filtrados / parciales , si están disponibles en su DBMS (necesitaría dos de ellos, en la Rtabla, para este caso). Esto es muy similar al tercer enfoque, excepto que no necesitará las 2 tablas adicionales. La restricción de "participación total" aún debe aplicarse por código.
No puedes directamente. Para empezar, no podría insertar el registro para A sin una B ya existente, pero no podría crear el registro B si no hay un registro A para él. Hay varias formas de aplicarlo utilizando elementos como los desencadenantes: debería verificar cada inserción y eliminar que al menos un registro correspondiente permanezca en la tabla de enlaces AB.
fuente