Bagaimana memiliki hubungan satu-ke-banyak dengan anak istimewa?

22

Saya ingin memiliki hubungan satu-ke-banyak di mana untuk setiap orang tua, satu atau nol anak ditandai sebagai "favorit." Namun, tidak setiap orang tua akan memiliki anak. (Pikirkan orang tua sebagai pertanyaan di situs ini, anak-anak sebagai jawaban, dan favorit sebagai jawaban yang diterima.) Misalnya,

TableA
    Id            INT PRIMARY KEY

TableB
    Id            INT PRIMARY KEY
    Parent        INT NOT NULL FOREIGN KEY REFERENCES TableA.Id

Cara saya melihatnya, saya bisa menambahkan kolom berikut ke TableA:

    FavoriteChild INT NULL FOREIGN KEY REFERENCES TableB.Id

atau kolom berikut ke TableB:

    IsFavorite    BIT NOT NULL

Masalah dengan pendekatan pertama adalah bahwa ia memperkenalkan kunci asing nullable, yang, saya mengerti, tidak dalam bentuk normal. Masalah dengan pendekatan kedua adalah bahwa lebih banyak pekerjaan yang harus dilakukan untuk memastikan bahwa paling banyak satu anak adalah favorit.

Kriteria apa yang harus saya gunakan untuk menentukan pendekatan mana yang harus digunakan? Atau, apakah ada pendekatan lain yang tidak saya pertimbangkan?

Saya menggunakan SQL Server 2012.

cubetwo1729
sumber

Jawaban:

19

Cara lain (tanpa Nulls dan tanpa siklus dalam FOREIGN KEYhubungan) adalah memiliki meja ketiga untuk menyimpan "anak-anak favorit". Di sebagian besar DBMS, Anda membutuhkan UNIQUEbatasan tambahan TableB.

@ Harun lebih cepat untuk mengidentifikasi bahwa konvensi penamaan di atas agak rumit dan dapat menyebabkan kesalahan. Biasanya lebih baik (dan akan membuat Anda tetap waras) jika Anda tidak memiliki Idkolom di seluruh tabel Anda dan jika kolom (yang digabungkan) memiliki nama yang sama di banyak tabel yang muncul. Jadi, inilah penggantian nama:

Parent
    ParentID        INT NOT NULL PRIMARY KEY

Child
    ChildID         INT NOT NULL PRIMARY KEY
    ParentID        INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
    UNIQUE (ParentID, ChildID)

FavoriteChild
    ParentID        INT NOT NULL PRIMARY KEY
    ChildID         INT NOT NULL 
    FOREIGN KEY (ParentID, ChildID) 
        REFERENCES Child (ParentID, ChildID)

Di SQL-Server (yang Anda gunakan), Anda juga memiliki opsi IsFavoritekolom bit yang Anda sebutkan. Anak favorit unik per orang tua dapat dicapai melalui Indeks Unik yang difilter:

Parent
    ParentID        INT NOT NULL PRIMARY KEY

Child
    ChildID         INT NOT NULL PRIMARY KEY
    ParentID        INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
    IsFavorite      BIT NOT NULL

CREATE UNIQUE INDEX is_FavoriteChild
  ON Child (ParentID)
  WHERE IsFavorite = 1 ;

Dan alasan utama bahwa opsi 1 Anda tidak disarankan, setidaknya tidak dalam SQL-Server, adalah bahwa pola jalur melingkar dalam referensi kunci asing memiliki beberapa masalah.

Baca artikel yang cukup lama: SQL By Design: The Circular Reference

Saat memasukkan atau menghapus baris dari dua tabel, Anda akan mengalami masalah "ayam-dan-telur". Tabel mana yang harus saya masukkan terlebih dahulu - tanpa melanggar batasan apa pun?

Untuk mengatasinya, Anda harus mendefinisikan setidaknya satu kolom nullable. (OK, secara teknis Anda tidak perlu, Anda dapat memiliki semua kolom sebagai NOT NULLtetapi hanya dalam DBMS, seperti Postgres dan Oracle, yang telah menerapkan batasan yang dapat ditangguhkan. Lihat jawaban @ Erwin dalam pertanyaan yang serupa: Kendala kunci asing kompleks di SQLAlchemy tentang cara ini bisa dilakukan di Postgres). Tetap saja, pengaturan ini terasa seperti berseluncur es tipis.

Periksa juga pertanyaan yang hampir sama pada SO (tetapi untuk MySQL) Dalam SQL, apakah boleh untuk dua tabel untuk merujuk satu sama lain? di mana jawaban saya hampir sama. MySQL tidak memiliki indeks parsial, sehingga satu-satunya pilihan yang layak adalah FK nullable dan solusi tabel tambahan.

ypercubeᵀᴹ
sumber
9

Itu tergantung pada apa prioritas Anda. Apakah Anda ingin menghindari pekerjaan atau Anda ingin mematuhi aturan normalisasi yang ketat?

Secara pribadi, saya pikir lebih baik ada IsFavoritedi meja anak, dan akan bersedia untuk memasukkan pekerjaan untuk memastikan bahwa paling banyak satu anak untuk setiap orang tua adalah favorit orang tua itu. Alasan utama tidak ada hubungannya dengan kunci asing kunci nullable: Saya tidak suka ide sama sekali kunci asing menunjuk ke dua arah.

Saran @ ypercube adalah kompromi yang bagus juga.

Sebagai tambahan, tolong, tolong, jangan sampah skema Anda dengan nama kolom yang tidak berarti seperti Id. Saya lebih suka melihat Idnama tersebut dengan cara yang bermakna di seluruh skema. Apakah ini mengidentifikasi penulis? Ok, sebut saja AuthorID. Apakah itu mewakili suatu produk? Ok, ProductID. Apakah itu seorang karyawan, dan dalam beberapa kasus referensi manajer? Oke, EmployeeIDdan ManagerIDlebih masuk akal bagi saya daripada IDdan Parent. Walaupun mungkin tampak logis untuk mengabaikan hal itu (dan berlebihan untuk memasukkannya), ketika Anda mulai menulis gabungan kompleks (atau memposting pertanyaan di sini), Anda pasti akan merasakan kutukan ketika Anda mencoba untuk merekayasa balik sekelompok gabungan pada saat itu ke kolom seperti a.Parent = b.ID... blecch.

Aaron Bertrand
sumber
1

Data termasuk dalam tabel anak. Kami tetap benar dengan pemicu di atas meja untuk memastikan satu dan hanya catatan yang ditandai sebagai favorit (atau dalam kasus kami sebagai alamat pilihan).

Namun, ide @ ypercube tentang tabel terpisah juga bagus.

HLGEM
sumber