Implementasi hubungan banyak ke banyak dengan batasan partisipasi total dalam SQL

17

Bagaimana saya harus menerapkan dalam SQL skenario yang digambarkan dalam diagram Entity-Relationship berikut?

Hubungan banyak-ke-banyak dengan batasan partisipasi total

Seperti yang ditunjukkan, setiap Akejadian jenis entitas harus terkait dengan setidaknya satu B mitra (ditunjukkan oleh garis penghubung ganda), dan sebaliknya . Saya tahu saya harus membuat tiga tabel berikut:

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

Tetapi, bagaimana dengan implementasi dari hambatan partisipasi total (yaitu, menegakkan bahwa setiap contoh dari salah satu Aatau Bterlibat dalam minimal satu kejadian hubungan dengan yang lain)?

John
sumber

Jawaban:

16

Ini tidak mudah dilakukan dalam SQL tetapi bukan tidak mungkin. Jika Anda ingin ini diberlakukan hanya melalui DDL, DBMS harus menerapkan DEFERRABLEbatasan. Ini dapat dilakukan (dan dapat diperiksa untuk bekerja di Postgres, yang telah mengimplementasikannya):

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

Sampai di sini adalah desain "normal", di mana setiap Adapat dikaitkan dengan nol, satu atau banyak Bdan setiap Bdapat dikaitkan dengan nol, satu atau banyak A.

Pembatasan "partisipasi total" membutuhkan kendala dalam urutan terbalik (dari Adan Bmasing - masing, referensi R). Memiliki FOREIGN KEYkendala dalam arah yang berlawanan (dari X ke Y dan dari Y ke X) membentuk lingkaran (masalah "ayam dan telur") dan itulah mengapa kita membutuhkan salah satunya DEFERRABLE. Dalam hal ini kita memiliki dua lingkaran ( A -> R -> Adan B -> R -> Bkarenanya kita membutuhkan dua batasan yang dapat ditangguhkan:

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

Kemudian kita dapat menguji bahwa kita dapat memasukkan data. Perhatikan bahwa INITIALLY DEFERREDtidak diperlukan. Kita dapat mendefinisikan kendala sebagai DEFERRABLE INITIALLY IMMEDIATEtetapi kemudian kita harus menggunakan SET CONSTRAINTSpernyataan untuk menunda mereka selama transaksi. Namun dalam setiap kasus, kita perlu memasukkan ke dalam tabel dalam satu transaksi:

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

Diuji di SQLfiddle .


Jika DBMS tidak memiliki DEFERRABLEkendala, satu solusi adalah mendefinisikan A (bid)dan B (aid)kolom sebagai NULL. The INSERTprosedur / pernyataan kemudian akan harus memasukkan pertama ke Adan B(menempatkan nulls di biddan aidmasing-masing), kemudian masukkan ke dalam Rdan kemudian memperbarui nilai-nilai null di atas untuk terkait nilai-nilai tidak nol dari R.

Dengan pendekatan ini, DBMS tidak menegakkan persyaratan oleh DDL saja tapi setiap INSERT(dan UPDATEdan DELETEdan MERGE) prosedur harus dipertimbangkan dan disesuaikan dan pengguna harus dibatasi untuk hanya menggunakan mereka dan tidak memiliki akses tulis langsung ke meja.

Memiliki lingkaran dalam FOREIGN KEYkendala tidak dianggap oleh banyak praktik terbaik dan karena alasan yang baik, kompleksitas menjadi salah satunya. Dengan pendekatan kedua misalnya (dengan kolom nullable), memperbarui dan menghapus baris masih harus dilakukan dengan kode tambahan, tergantung pada DBMS. Dalam SQL Server misalnya, Anda tidak bisa hanya meletakkan ON DELETE CASCADEkarena pembaruan dan penghapusan kaskade tidak diizinkan ketika ada lingkaran FK.

Harap baca juga jawaban di pertanyaan terkait ini:
Bagaimana memiliki hubungan satu-ke-banyak dengan anak istimewa?


Pendekatan ketiga yang lain (lihat jawaban saya dalam pertanyaan yang disebutkan di atas) adalah menghapus FK sirkuler sepenuhnya. Jadi, pertahankan bagian pertama dari kode (dengan tabelA , B, Rdan kunci asing hanya dari R ke A dan B) hampir utuh (sebenarnya menyederhanakan itu), kita tambahkan meja lain untuk Amenyimpan "harus memiliki satu" item terkait dari B. Jadi, A (bid)kolom pindah ke A_one (bid)Hal yang sama dilakukan untuk hubungan terbalik dari B ke 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
 );

Perbedaan atas pendekatan 1 dan 2 adalah bahwa tidak ada FK melingkar, sehingga pembaruan dan penghapusan yang mengalir akan bekerja dengan baik. Penegakan "partisipasi total" tidak hanya dengan DDL, seperti pada pendekatan ke-2, dan harus dilakukan dengan prosedur yang sesuai ( INSERT/UPDATE/DELETE/MERGE). Perbedaan kecil dengan pendekatan ke-2 adalah bahwa semua kolom dapat didefinisikan tidak dapat dibatalkan.


Pendekatan keempat yang lain (lihat jawaban @Aaron Bertrand dalam pertanyaan yang disebutkan di atas) adalah menggunakan indeks unik yang difilter / parsial , jika tersedia dalam DBMS Anda (Anda akan membutuhkan dua di antaranya, dalam Rtabel, untuk kasus ini). Ini sangat mirip dengan pendekatan ke-3, kecuali bahwa Anda tidak akan membutuhkan 2 tabel tambahan. Batasan "partisipasi total" masih harus diterapkan oleh kode.

ypercubeᵀᴹ
sumber
Pendekatan ke-4 (agak tersembunyi) sebenarnya sempurna. Sebagai contoh, lihat postgresql.org/docs/9.6/static/indexes-partial.html Contoh 11-3 untuk postgres.
Danilo
@Danilo Saya melihat bagaimana itu sempurna untuk memastikan bahwa ada total partisipasi maksimum 1 (berdasarkan beberapa bidang tambahan - sukses dalam contoh postgre). Saya tidak dapat melihat bagaimana membantu memastikan bahwa setidaknya ada satu keberhasilan - pertanyaan aktual di utas ini. Bisakah Anda jelaskan?
Alexander Mihailov
3

Anda tidak bisa secara langsung. Sebagai permulaan, Anda tidak akan bisa menyisipkan catatan untuk A tanpa B yang sudah ada, tetapi Anda tidak bisa membuat catatan B jika tidak ada catatan A untuk itu. Ada beberapa cara untuk menerapkannya menggunakan hal-hal seperti pemicu - Anda harus memeriksa setiap sisipan dan menghapus bahwa setidaknya satu catatan yang sesuai tetap ada di tabel tautan AB.

Silindris
sumber