Dairesel yabancı anahtar referanslara sahip olmak kabul edilebilir mi \ Onlardan nasıl kaçınılır?


29

Yabancı anahtar alanındaki iki tablo arasında dairesel bir referans olması kabul edilebilir mi?

Olmazsa, bu durumlar nasıl önlenebilir?

Öyleyse, veriler nasıl eklenebilir?

Aşağıda, (benim görüşüme göre) dairesel bir referansın kabul edilebileceği yerlerin bir örneği verilmiştir:

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
" Öyleyse, veriler nasıl eklenebilir " - kullanılan DBMS'ye bağlıdır. Örneğin Postgres, Oracle, SQLite ve Apache Derby, bunu mümkün kılacak ertelenebilir kısıtlamalara izin veriyor. Diğer DBMS ile (Ama hala ilk etapta böyle bir kısıtlama ihtiyacını itiraz olurdu) dışarı şans
a_horse_with_no_name

Yanıtlar:


12

Yabancı anahtarlar için null alanlarını kullandığınızdan, aslında sizin öngördüğünüz şekilde doğru çalışan bir sistem oluşturabilirsiniz. Hesaplar tablosuna satır eklemek için, Kayıtlar tablosunda boş bir PrimaryContactID olan Hesaplara eklenmesine izin vermediğiniz sürece bir satırınız olması gerekir. Zaten bir Hesap satırına sahip olmadan bir irtibat satırı oluşturmak için, Kişiler tablosundaki Hesap Kimliği sütununun geçersiz olmasına izin vermelisiniz. Bu, Hesapların hiçbir kişilere sahip olmamasını ve Kişilerin hiç hesap sahibi olmamasına izin verir. Belki de bu arzu edilir, belki de değildir.

Bunu söyledikten sonra, kişisel tercihim şu kurulumu yapmak olacaktır:

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;

Bu yetenek sağlar:

  1. Pieter'in cevabında önerdiği şekilde çapraz referans tablosu üzerinden irtibatlar ve hesaplar arasındaki ilişkileri açıkça belirtin
  2. Referans bütünlüğünü sağlam, dairesel olmayan bir şekilde koruyun.
  3. İndeks aracılığıyla birincil düzeyde irtibatlandırılabilecek birincil irtibatlar listesi sağlayın IX_AccountsContactsXRef_Primary. Bu dizin bir filtre içerir, bu nedenle yalnızca onları destekleyen platformlarda çalışır. Bu dizin UNIQUEseçeneği ile belirtildiğinden, her hesap için yalnızca tek bir birincil bağlantı olabilir.

Örneğin, tüm kişilerin listesini görüntülemek istiyorsanız, "birincil" durumunu belirten ve her bir hesap için listenin başında birincil kişileri gösteren bir sütunu olan bir sütun görüntülemek isterseniz, şunları yapabilirsiniz:

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;

Filtrelenmiş dizin, hesap başına tek bir birincil temastan daha fazlasını eklemeyi önlerken, aynı anda birincil temasların bir listesini hızlı bir şekilde geri gönderme yöntemi sağlar. IsActiveHesap başına kişi geçmişini korumak için benzersiz olmayan filtrelenmiş bir dizine sahip başka bir sütunu kolayca düşünebilirsiniz , hatta bu kişi artık hesapla ilişkilendirilmezse:

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
Genel olarak, dairesel referanslardan kaçınılması gerektiğini söyler misiniz? Kötü olmadıkları ve etkili tasarımlar yapmak için kullandıkları kanısındayım. Onlar-ebilmek-si olmak biraz daha karmaşık siler yapmak ve aksi takdirde-ebeveyne-üstü varlık var NULL güncelleme, ancak kolaylık için ödemek için düşük bir fiyat buluyorum. Bunları FK alanının null olduğu Postgres'te kullanıyorum, bu yüzden NULL ile bir satır
yarattım

Dairesel referanslardan hoşlanmıyorum, çünkü tasarımı gereksiz yere karmaşıklaştırmaya meyillidirler ve çoğu zaman takas için önemli bir performans avantajı sunmaz. Occam Razor hayranıyım ve sonuç olarak verilen bir problem için en basit çözüme yöneliyor.
Max Vernon,

1
Tamamen Occam'ın tıraş bıçağıyım ama yukarıda açıklanan tasarım, 3. normal formu zorunlu olarak ihlal etmemekle birlikte bazı 2. sorguları veya birleşmeleri engellememe izin verdi. Geri bildiriminiz için teşekkür ederiz
amphibient

6

Hayır, dairesel yabancı anahtar referanslara sahip olmak kabul edilemez. Sadece, kısıtlamayı sürekli düşürmeden ve yeniden yaratmadan veri eklemek mümkün olamayacağından. Ancak, düşünebildiğim her alanın temelde kusurlu bir modeli olduğu için. Örneğinizde, Hesap ile İletişim arasındaki ilişkinin NN olmadığı, FK Hesaplarıyla bir bağlantı tablosu gerektiren, hem Hesap hem de İletişim'e geri dönmesini gerektiren hiçbir etki alanı düşünemiyorum.

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
" veri eklemek imkansız olur " - hayır, imkansız olmazdı. Sadece kısıtlamaları ertelenebilir olarak ilan edin. Ancak aynı fikirdeyim: hemen hemen her durumda dairesel referanslar kötü bir tasarımdır.
a_horse_with_no_name

3
@ a_horse - SQL Server'da ertelenebilir bir referans tanımlamak mümkün değil ... Oracle'da yapabileceğinizi biliyorum, sadece tutarsızlığı belirtmek istedim.
Max Vernon

2
@MaxVernon: soru sadece SQL Server ile ilgili değil ve yalnızca Oracle'dan ertelenebilir kısıtlamaları destekleyen daha fazla DBMS var - ama dediğim gibi: Pieter ile tasarımın kendisinin yanlış olduğu konusunda hemfikirim (ve çözümü çok daha mantıklı)
a_horse_with_no_name

4
Herhangi bir örneğin özelliklerini bir kenara bırakmak, genel anlamda, karşılıklı (yani "dairesel") referans bütünlük kısıtlamalarına sahip olmak konusunda mutlaka yanlış veya "kusurlu" bir şey yoktur. Bu aslında bir Katılma Bağımlılığı örneğidir. Birleştirme bağımlılıkları, DBMS'niz bunları uygulamanıza izin veriyorsa, prensipte iyi bir şeydir. Sadece SQL DBMS'lerde tablolar arasında karmaşık bağımlılıklar uygulamak çok kolay değildir.
nvogel

6
@Pieter, 1-1 bir katılım bağımlılığının tek örneği değildir ve özellikle özel bir durum bile değildir. Katılma bağımlılık kısıtlamalarının mükemmel bir anlam ifade ettiği durumlar vardır.
nvogel

1

Harici nesnenizin hesap yerine birincil kişiyi göstermesini sağlayabilirsiniz. Verileriniz şuna benzer:

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)
)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.