Misalkan kita memiliki tabel yang memiliki batasan kunci asing untuk dirinya sendiri, seperti:
CREATE TABLE Foo
(FooId BIGINT PRIMARY KEY,
ParentFooId BIGINT,
FOREIGN KEY([ParentFooId]) REFERENCES Foo ([FooId]) )
INSERT INTO Foo (FooId, ParentFooId)
VALUES (1, NULL), (2, 1), (3, 2)
UPDATE Foo SET ParentFooId = 3 WHERE FooId = 1
Tabel ini akan memiliki catatan berikut:
FooId ParentFooId
----- -----------
1 3
2 1
3 2
Ada kasus-kasus di mana desain semacam ini bisa masuk akal (misalnya hubungan "karyawan-dan-bos-karyawan" yang khas), dan dalam kasus apa pun: Saya berada dalam situasi di mana saya memiliki ini dalam skema saya.
Sayangnya, jenis desain ini memungkinkan untuk melingkar dalam catatan data, seperti ditunjukkan pada contoh di atas.
Pertanyaan saya kemudian adalah:
- Apakah mungkin untuk menulis batasan yang memeriksa ini? dan
- Apakah layak untuk menulis batasan yang memeriksa ini? (jika hanya perlu sampai kedalaman tertentu)
Untuk bagian (2) dari pertanyaan ini, mungkin relevan untuk menyebutkan bahwa saya mengharapkan hanya ratusan atau mungkin dalam beberapa kasus ribuan catatan dalam tabel saya, biasanya tidak bersarang lebih dalam dari level sekitar 5 hingga 10.
PS. MS SQL Server 2008
Pembaruan 14 Maret 2012
Ada beberapa jawaban bagus. Saya sekarang telah menerima salah satu yang membantu saya memahami kemungkinan / kelayakan yang disebutkan. Ada beberapa jawaban hebat lainnya, beberapa dengan saran implementasi juga, jadi jika Anda mendarat di sini dengan pertanyaan yang sama, lihat semua jawaban;)
sumber
HIERARCHYID
yang tampaknya merupakan implementasi MSSQL2008 asli dari model himpunan bersarang.Saya telah melihat 2 cara utama untuk menegakkan ini:
1, jalan LAMA:
Kolom FooHierarchy akan berisi nilai seperti ini:
Di mana angka dipetakan ke kolom FooId. Anda kemudian akan menegakkan bahwa kolom Hierarki berakhir dengan "| id" dan seluruh string cocok dengan FooHieratchy dari PARENT.
2, cara BARU:
SQL Server 2008 memiliki tipe data baru yang disebut HierarchyID , yang melakukan semua ini untuk Anda.
Ini beroperasi pada prinsip yang sama dengan cara LAMA, tetapi ditangani secara efisien oleh SQL Server, dan cocok untuk digunakan sebagai PENGGANTIAN untuk kolom "ParentID" Anda.
sumber
HIERARCHYID
mencegah pembuatan hierarki loop?Ini agak mungkin: Anda dapat memanggil UDF skalar dari Anda PERIKSA kendala, dan itu semacam dapat mendeteksi siklus berapa pun panjangnya. Sayangnya, pendekatan ini sangat lambat dan tidak dapat diandalkan: Anda dapat memiliki positif palsu dan negatif palsu.
Sebaliknya, saya akan menggunakan jalur terwujud.
Cara lain untuk menghindari siklus adalah memiliki PERIKSA (ID> ParentID), yang mungkin juga sangat tidak layak.
Namun cara lain untuk menghindari siklus adalah menambahkan dua kolom lagi, LevelInHierarchy dan ParentLevelInHierarchy, merujuk (ParentID, ParentLevelInHierarchy) merujuk ke (ID, LevelInHierarchy), dan memiliki PERIKSA (LevelInHierarchy> ParentLevelInHierarchy).
sumber
Saya percaya itu mungkin:
Saya mungkin telah melewatkan sesuatu (maaf, saya tidak dapat mengujinya secara menyeluruh), tetapi sepertinya berhasil.
sumber
Berikut adalah pilihan lain: pemicu yang memungkinkan pembaruan multi-baris dan tidak memaksakan siklus. Ia bekerja dengan melintasi rantai leluhur sampai menemukan elemen root (dengan orangtua NULL), sehingga membuktikan tidak ada siklus. Ini terbatas pada 10 generasi karena siklus tentu saja tidak ada habisnya.
Ini hanya bekerja dengan set baris yang dimodifikasi saat ini, sehingga selama pembaruan tidak menyentuh sejumlah besar item yang sangat dalam di tabel, kinerja seharusnya tidak terlalu buruk. Itu harus pergi semua jalan sampai rantai untuk setiap elemen, sehingga akan memiliki beberapa dampak kinerja.
Pemicu yang benar-benar "cerdas" akan mencari siklus secara langsung dengan memeriksa untuk melihat apakah suatu barang mencapai dirinya sendiri dan kemudian menyerah. Namun, ini membutuhkan keadaan pemeriksaan semua node yang ditemukan sebelumnya selama setiap loop dan dengan demikian mengambil loop WHILE dan lebih banyak pengkodean daripada yang ingin saya lakukan sekarang. Ini seharusnya tidak benar-benar lebih mahal karena operasi normal adalah tidak memiliki siklus dan dalam hal ini akan lebih cepat bekerja hanya dengan generasi sebelumnya daripada semua node sebelumnya selama setiap loop.
Saya ingin masukan dari @AlexKuznetsov atau siapa pun tentang bagaimana hal ini akan terjadi dalam isolasi snapshot. Saya menduga itu tidak terlalu baik, tetapi ingin memahaminya dengan lebih baik.
Memperbarui
Saya menemukan cara untuk menghindari tambahan bergabung kembali ke tabel Dimasukkan. Jika ada yang melihat cara yang lebih baik untuk melakukan GROUP BY untuk mendeteksi mereka yang tidak mengandung NULL, beri tahu saya.
Saya juga menambahkan peralihan ke READ COMMITTED jika sesi saat ini di tingkat ISOLASI SNAPSHOT. Ini akan mencegah inkonsistensi, meskipun sayangnya akan menyebabkan pemblokiran yang meningkat. Itu agak tidak bisa dihindari untuk tugas yang dihadapi.
sumber
Jika catatan Anda bersarang lebih dari 1 tingkat, batasan tidak akan berfungsi (saya berasumsi maksud Anda misalnya catatan 1 adalah induk dari catatan 2, dan catatan 3 adalah induk dari catatan 1). Satu-satunya cara untuk melakukan ini adalah dengan kode induk atau dengan pemicu, tetapi jika Anda melihat tabel besar dan beberapa level ini akan cukup intensif.
sumber