Ini tidak mudah dilakukan dalam SQL tetapi bukan tidak mungkin. Jika Anda ingin ini diberlakukan hanya melalui DDL, DBMS harus menerapkan DEFERRABLE
batasan. 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 A
dapat dikaitkan dengan nol, satu atau banyak B
dan setiap B
dapat dikaitkan dengan nol, satu atau banyak A
.
Pembatasan "partisipasi total" membutuhkan kendala dalam urutan terbalik (dari A
dan B
masing - masing, referensi R
). Memiliki FOREIGN KEY
kendala 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 -> A
dan B -> R -> B
karenanya 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 DEFERRED
tidak diperlukan. Kita dapat mendefinisikan kendala sebagai DEFERRABLE INITIALLY IMMEDIATE
tetapi kemudian kita harus menggunakan SET CONSTRAINTS
pernyataan 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 DEFERRABLE
kendala, satu solusi adalah mendefinisikan A (bid)
dan B (aid)
kolom sebagai NULL
. The INSERT
prosedur / pernyataan kemudian akan harus memasukkan pertama ke A
dan B
(menempatkan nulls di bid
dan aid
masing-masing), kemudian masukkan ke dalam R
dan 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 UPDATE
dan DELETE
dan 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 KEY
kendala 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 CASCADE
karena 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
, R
dan kunci asing hanya dari R ke A dan B) hampir utuh (sebenarnya menyederhanakan itu), kita tambahkan meja lain untuk A
menyimpan "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 R
tabel, 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.