Впровадження взаємозв'язку «багато-багато» із загальними обмеженнями участі в SQL


17

Як я можу реалізувати в SQL сценарій, зображений на наступній схемі співвідношення особи-особи?

Взаємовідносини багатьох до загальних обмежень участі

Як показано, кожен Aтип типу сутності повинен бути пов'язаний щонайменше з одним B аналогом (позначений подвійними сполучними лініями), і навпаки . Я знаю, що я повинен створити три наступні таблиці:

    CREATE TABLE A
    (
        a INT NOT NULL,
        CONSTRAINT A_PK PRIMARY KEY (a)
    );

    CREATE TABLE B
    (
        b INT NOT NULL,
        CONSTRAINT B_PK PRIMARY KEY (b)
    );

    CREATE TABLE R
    (
        a INT NOT NULL,
        b INT NOT NULL,
        CONSTRAINT R_PK      PRIMARY KEY (a, b),
        CONSTRAINT R_to_A_FK FOREIGN KEY (a)
            REFERENCES A (a),
        CONSTRAINT R_to_B_FK FOREIGN KEY (b)
            REFERENCES B (b)
    );

Але то , що про здійснення спільної участі обмежень (тобто виконання , що кожен екземпляр або Aабо Bбере участь в протягом мінімум одного виникнення відносин з іншого)?

Відповіді:


16

Зробити це в 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 поодинці, але кожну процедуру INSERTUPDATEта 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 додаткові таблиці. Обмеження "загальної участі" все ще слід застосовувати за кодом.


Четвертий підхід (трохи прихований подалі) насправді ідеальний. Як приклад, див. Postgresql.org/docs/9.6/static/indexes-partial.html Приклад 11-3 для постгресів.
Данило

@Danilo Я бачу, як ідеально забезпечити максимум 1 загальну участь (на основі якогось додаткового поля - успіх у прикладі постгре). Я не бачу, як це допоможе забезпечити хоча б один успіх - власне питання в цій темі. Чи можете ви, будь ласка, докладно?
Олександр Михайлов

3

Ви не можете безпосередньо. Для початку ви не зможете вставити запис для A без B, який вже існує, але ви не змогли створити запис B, якщо для нього немає запису A. Існує декілька способів примусового застосування цих речей, як тригери - вам доведеться перевіряти кожну вставку та видаляти, що принаймні одна відповідна запис залишається у таблиці посилань AB.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.