Зробити це в SQL непросто, але це неможливо. Якщо ви хочете, щоб це виконувалося лише через DDL, СУБД має мати реалізовані DEFERRABLEобмеження. Це можна зробити (і можна перевірити, щоб вони працювали в Postgres, який їх реалізував):
-- 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
);
До цього часу йдеться про "звичайний" дизайн, де кожен Aможе бути пов'язаний з нулем, один або багато, Bі кожен Bможе бути пов'язаний з нулем, один або багато A.
Обмеження «спільна участь» потребує обмеження в зворотному порядку (від Aі , Bвідповідно, посилання R). Маючи FOREIGN KEYобмеження в протилежних напрямках (від X до Y і від Y до X) - це формування кола (проблема "курка і яйце"), і саме тому нам потрібен хоча б один з них DEFERRABLE. У цьому випадку у нас є два кола ( A -> R -> Aі B -> R -> Bтому нам потрібні два відкладені обмеження:
-- 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 ;
Тоді ми можемо перевірити, чи можемо ми вставити дані. Зауважте, що INITIALLY DEFERREDце не потрібно. Ми могли б визначити обмеження як, DEFERRABLE INITIALLY IMMEDIATEале тоді нам доведеться використовувати SET CONSTRAINTSоператор, щоб відкласти їх під час транзакції. Однак у кожному випадку нам потрібно вставляти в таблиці одну транзакцію:
-- 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 ;
Тестовано на SQLfiddle .
Якщо СУБД не має DEFERRABLEобмежень, одним вирішенням є визначення A (bid)та B (aid)стовпців як NULL. Потім INSERTпроцедури / оператори повинні спершу вставити в ( Aі Bввести нулі bidі aidвідповідно), потім вставити в, Rа потім оновити нульові значення вище до відповідних ненульових значень з R.
При такому підході СУБД не виконує вимоги DDL поодинці, але кожну процедуру INSERT(і UPDATEта DELETEта MERGE) необхідно враховувати та відповідно коригувати, а користувачі повинні обмежуватись лише ними та не мати прямого доступу для запису до таблиць.
Наявність кіл із FOREIGN KEYобмеженнями багатьма не вважається найкращою практикою і з поважних причин, складність - одна з них. Наприклад, при другому підході (з нульовими стовпцями), оновлення та видалення рядків все одно доведеться робити з додатковим кодом, залежно від СУБД. Наприклад, у SQL Server ви не можете просто поставити, ON DELETE CASCADEоскільки каскадні оновлення та видалення заборонені, коли є FK-кола.
Будь ласка, прочитайте також відповіді на це пов’язане запитання:
Як мати стосунки з багатьма особами з привілейованою дитиною?
Інший, третій підхід (див. Мою відповідь у вищезгаданому запитанні) полягає в тому, щоб повністю видалити кругові ФК. Таким чином, зберігаючи першу частину коду (з таблицями A, B, Rі зовнішні ключі тільки від R до А і В) майже недоторканими ( на насправді спрощує його), ми додамо ще одну таблицю для Aзберігання «повинен мати один» пов'язаний елемент з B. Отже, A (bid)стовпець переміщується до A_one (bid)Те саме робиться для зворотного відношення від B до 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
);
Відмінність від 1-го і 2-го підходу полягає в тому, що кругових FK не існує, тому каскадні оновлення та видалення будуть працювати чудово. Забезпечення "повної участі" відбувається не лише DDL, як у другому підході, а повинно здійснюватися відповідною процедурою ( INSERT/UPDATE/DELETE/MERGE). Незначна відмінність від другого підходу полягає в тому, що всі стовпці можна визначити не нульовими.
Інший, четвертий підхід (див. Відповідь @Aaron Bertrand у вищезгаданому запитанні) полягає у використанні відфільтрованих / часткових унікальних індексів, якщо вони доступні у вашій СУБД (для цього вам знадобляться два з них у Rтаблиці). Це дуже схоже на 3-й підхід, за винятком того, що вам не знадобляться 2 додаткові таблиці. Обмеження "загальної участі" все ще слід застосовувати за кодом.