Apakah dapat diterima untuk memiliki referensi kunci asing melingkar \ Bagaimana menghindarinya?


29

Apakah dapat diterima untuk memiliki referensi melingkar antara dua tabel di bidang kunci asing?

Jika tidak, bagaimana situasi ini dapat dihindari?

Jika demikian, bagaimana cara memasukkan data?

Di bawah ini adalah contoh di mana (menurut saya) referensi melingkar dapat diterima:

CREATE TABLE Account
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50)
)

CREATE TABLE Contact
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50),
    AccountID INT FOREIGN KEY REFERENCES Account(ID)
)

ALTER TABLE Account ADD PrimaryContactID INT FOREIGN KEY REFERENCES Contact(ID)

2
" Jika demikian, bagaimana cara memasukkan data " - tergantung pada DBMS yang digunakan. Postgres, Oracle, SQLite dan Apache Derby misalnya memungkinkan batasan yang dapat ditangguhkan yang akan memungkinkan hal ini. Dengan DBMS lain Anda kurang beruntung (Tapi saya masih akan membantah perlunya kendala seperti di tempat pertama)
a_horse_with_no_name

Jawaban:


12

Karena Anda menggunakan bidang yang dapat dibatalkan untuk kunci asing, Anda sebenarnya dapat membangun sistem yang berfungsi dengan benar seperti yang Anda bayangkan. Untuk memasukkan baris ke dalam tabel Akun, Anda harus memiliki baris yang ada di tabel Kontak kecuali Anda mengizinkan sisipan ke dalam Akun dengan null PrimaryContactID. Untuk membuat baris kontak tanpa harus memiliki baris Akun, Anda harus membiarkan kolom AccountID di tabel Kontak menjadi nullable. Ini memungkinkan Akun tidak memiliki kontak, dan memungkinkan Kontak tidak memiliki akun. Mungkin ini diinginkan, mungkin tidak.

Karena itu, preferensi pribadi saya adalah memiliki pengaturan berikut:

CREATE TABLE dbo.Accounts
(
    AccountID INT NOT NULL
        CONSTRAINT PK_Accounts
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , AccountName VARCHAR(255)
);

CREATE TABLE dbo.Contacts
(
    ContactID INT NOT NULL
        CONSTRAINT PK_Contacts
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , ContactName VARCHAR(255)
);

CREATE TABLE dbo.AccountsContactsXRef
(
    AccountsContactsXRefID INT NOT NULL
        CONSTRAINT PK_AccountsContactsXRef
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , AccountID INT NOT NULL
        CONSTRAINT FK_AccountsContactsXRef_AccountID
        FOREIGN KEY REFERENCES dbo.Accounts(AccountID)
    , ContactID INT NOT NULL
        CONSTRAINT FK_AccountsContactsXRef_ContactID
        FOREIGN KEY REFERENCES dbo.Contacts(ContactID)
    , IsPrimary BIT NOT NULL 
        CONSTRAINT DF_AccountsContactsXRef
        DEFAULT ((0))
    , CONSTRAINT UQ_AccountsContactsXRef_AccountIDContactID
        UNIQUE (AccountID, ContactID)
);

CREATE UNIQUE INDEX IX_AccountsContactsXRef_Primary
ON dbo.AccountsContactsXRef(AccountID, IsPrimary)
WHERE IsPrimary = 1;

Ini memberikan kemampuan untuk:

  1. Gambarkan dengan jelas hubungan antara kontak dan akun melalui tabel referensi silang seperti yang direkomendasikan Pieter dalam jawabannya
  2. Pertahankan integritas referensial dengan cara yang sehat dan tidak melingkar.
  3. Berikan daftar kontak utama yang sangat terpelihara melalui IX_AccountsContactsXRef_Primaryindeks. Indeks ini berisi filter, sehingga hanya akan berfungsi pada platform yang mendukungnya. Karena indeks ini ditentukan dengan UNIQUEopsi, hanya ada satu kontak utama untuk setiap akun.

Misalnya, jika Anda ingin menampilkan daftar semua kontak, dengan kolom yang menunjukkan status "primer", menampilkan kontak utama di bagian atas daftar untuk setiap Akun, Anda dapat melakukan:

SELECT A.AccountName
    , C.ContactName
    , XR.IsPrimary
FROM dbo.Accounts A
    INNER JOIN dbo.AccountsContactsXRef XR ON A.AccountID = XR.AccountID
    INNER JOIN dbo.Contacts C ON XR.ContactID = C.ContactID
ORDER BY A.AccountName
    , XR.IsPrimary DESC
    , C.ContactName;

Indeks yang difilter mencegah penyisipan lebih dari satu kontak utama per akun, sementara secara bersamaan memberikan metode cepat untuk mengembalikan daftar kontak utama. Seseorang dapat dengan mudah membayangkan kolom lain, IsActivedengan indeks yang disaring tidak unik untuk mempertahankan riwayat kontak per akun, bahkan setelah kontak itu tidak lagi dikaitkan dengan akun:

ALTER TABLE dbo.AccountsContactsXRef
ADD IsActive BIT NOT NULL
CONSTRAINT DF_AccountsContactsXRef_IsActive
DEFAULT ((1));

CREATE INDEX IX_AccountsContactsXRef_IsActive
ON dbo.AccountsContactsXRef(IsActive)
WHERE IsActive = 1;

1
akankah Anda mengatakan, secara umum, bahwa referensi melingkar harus dihindari? Saya berpendapat bahwa mereka tidak buruk dan telah menggunakannya untuk mencapai desain yang efektif. Mereka membuat penghapusan sedikit lebih rumit dalam yang mereka butuhkan dan perbarui ke NULL dalam entitas induk yang hanya akan menjadi orang tua, tetapi saya menemukan bahwa menjadi harga rendah untuk membayar kenyamanan. Saya menggunakannya di Postgres, di mana bidang FK nullable jadi saya membuat baris dengan itu NULL dan kemudian memperbarui bidang FK ke PK dari tabel anak untuk cukup banyak mencapai fungsi yang sama seperti yang dijelaskan dalam OP
amphibient

Saya tidak suka referensi sirkuler hanya karena mereka cenderung mempersulit desain, dan sebagian besar waktu tidak menawarkan manfaat kinerja yang signifikan sepadan dengan trade-off. Saya penggemar Occam's Razor, dan sebagai hasilnya cenderung ke solusi paling sederhana untuk masalah yang diberikan.
Max Vernon

1
Saya semua untuk pisau cukur Occam tetapi desain yang dijelaskan di atas memungkinkan saya untuk menghindari beberapa pertanyaan atau bergabung 2 sementara tidak selalu melanggar bentuk normal ke-3. Saya menghargai tanggapan Anda
amfibi

6

Tidak, tidak dapat diterima untuk memiliki referensi kunci asing melingkar. Bukan hanya karena tidak mungkin memasukkan data tanpa terus-menerus menjatuhkan dan menciptakan kembali kendala. tetapi karena itu adalah model cacat mendasar dari setiap dan setiap domain yang dapat saya pikirkan. Dalam contoh Anda, saya tidak dapat memikirkan domain mana pun di mana hubungan antara Akun dan Kontak bukan NN, membutuhkan tabel persimpangan dengan referensi FK kembali ke Akun dan Kontak.

CREATE TABLE Account
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50)
)

CREATE TABLE Contact
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50),
)

CREATE TABLE AccountContact
(
    AccountID INT FOREIGN KEY REFERENCES Account(ID),
    ContactID INT FOREIGN KEY REFERENCES Contact(ID),

    primary key(AccountID,ContactID)
)

5
" tidak mungkin untuk memasukkan data " - tidak, itu tidak mustahil. Hanya menyatakan kendala sebagai ditangguhkan. Tapi saya setuju: dalam hampir semua kasus, referensi melingkar adalah desain yang buruk.
a_horse_with_no_name

3
@ a_horse - tidak mungkin untuk menentukan referensi yang tertunda dalam SQL Server ... Saya tahu Anda bisa di Oracle, hanya ingin menunjukkan perbedaannya.
Max Vernon

2
@ MaxVernon: pertanyaannya bukan hanya tentang SQL Server dan ada lebih banyak DBMS dari hanya Oracle yang mendukung kendala yang ditangguhkan - tetapi seperti yang saya katakan: Saya setuju dengan Pieter bahwa desainnya sendiri salah (dan solusinya jauh lebih masuk akal)
a_horse_with_no_name

4
Mengesampingkan spesifik dari satu contoh, secara umum tidak ada yang salah atau "cacat" tentang memiliki kendala integritas referensial timbal balik (yaitu "lingkaran"). Ini sebenarnya hanyalah sebuah contoh dari Ketergantungan Bergabung. Bergabung dengan dependensi adalah hal yang baik pada prinsipnya jika DBMS Anda memungkinkan Anda untuk mengimplementasikannya. Hanya saja dalam SQL DBMS tidak mudah untuk mengimplementasikan dependensi kompleks antar tabel.
nvogel

6
@Pieter, 1-1 bukan satu-satunya contoh ketergantungan bergabung, dan itu bahkan bukan kasus khusus. Ada beberapa kasus di mana kendala ketergantungan gabungan masuk akal.
nvogel

1

Anda dapat mengarahkan objek eksternal ke kontak utama, bukan ke akun. Data Anda akan terlihat seperti ini:

CREATE TABLE Account
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50)
)

CREATE TABLE Contact
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50),
    AccountID INT FOREIGN KEY REFERENCES Account(ID)
)

CREATE TABLE AccountOwner (
    Other Stuff Here . . .
    PrimaryContactID INT FOREIGN KEY REFERENCES Contact(ID)
)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.