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.