Birden çok tabloya Yabancı Anahtar


127

Veritabanımda 3 alakalı tablo var.

CREATE TABLE dbo.Group
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)  

CREATE TABLE dbo.User
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)

CREATE TABLE dbo.Ticket
(
    ID int NOT NULL,
    Owner int NOT NULL,
    Subject varchar(50) NULL
)

Kullanıcılar birden çok gruba aittir. Bu, çoktan çoğa ilişki yoluyla yapılır, ancak bu durumda önemsizdir. Bir bilet, dbo.Ticket.Owner alanı aracılığıyla bir gruba veya bir kullanıcıya ait olabilir.

Bir bilet ve isteğe bağlı olarak bir kullanıcı veya bir grup arasındaki bu ilişkiyi en DOĞRU tanımlamanın EN DOĞRU yolu ne olabilir ?

Bilet tablosuna hangi türün sahibi olduğunu söyleyen bir bayrak eklemem gerektiğini düşünüyorum.


Bana göre her bilet bir gruba ait. Sadece bir kullanıcı, bir gruptur. @ Nathan-skerl modellerinden hangi seçim 4. Kılavuzları anahtar olarak kullanırsanız, her şey de oldukça iyi çalışır
GraemeMiller

Yanıtlar:


149

Hepsi "doğruluk" ve kullanım kolaylığı açısından değişen birkaç seçeneğiniz var. Her zaman olduğu gibi, doğru tasarım ihtiyaçlarınıza bağlıdır.

  • Ticket, OwnedByUserId ve OwnedByGroupId'de iki sütun oluşturabilir ve her tablo için null yapılabilir Yabancı Anahtarlara sahip olabilirsiniz.

  • Hem bilet: kullanıcı hem de bilet: grup ilişkilerini etkinleştiren M: M referans tabloları oluşturabilirsiniz. Belki de gelecekte tek bir biletin birden fazla kullanıcıya veya gruba ait olmasına izin vermek isteyeceksiniz? Bu tasarım, bilet olduğunu zorlamaz gerekir sadece tek bir varlık ait olması.

  • Her kullanıcı için varsayılan bir grup oluşturabilir ve yalnızca gerçek bir Gruba veya bir Kullanıcının varsayılan Grubuna ait biletleriniz olabilir.

  • Veya (benim seçimim), hem Kullanıcılar hem de Gruplar için bir temel görevi gören ve bu varlığa ait biletleri olan bir varlığı modelleyin.

Gönderdiğiniz şemayı kullanarak kaba bir örnek:

create table dbo.PartyType
(   
    PartyTypeId tinyint primary key,
    PartyTypeName varchar(10)
)

insert into dbo.PartyType
    values(1, 'User'), (2, 'Group');


create table dbo.Party
(
    PartyId int identity(1,1) primary key,
    PartyTypeId tinyint references dbo.PartyType(PartyTypeId),
    unique (PartyId, PartyTypeId)
)

CREATE TABLE dbo.[Group]
(
    ID int primary key,
    Name varchar(50) NOT NULL,
    PartyTypeId as cast(2 as tinyint) persisted,
    foreign key (ID, PartyTypeId) references Party(PartyId, PartyTypeID)
)  

CREATE TABLE dbo.[User]
(
    ID int primary key,
    Name varchar(50) NOT NULL,
    PartyTypeId as cast(1 as tinyint) persisted,
    foreign key (ID, PartyTypeId) references Party(PartyID, PartyTypeID)
)

CREATE TABLE dbo.Ticket
(
    ID int primary key,
    [Owner] int NOT NULL references dbo.Party(PartyId),
    [Subject] varchar(50) NULL
)

7
Kullanıcı / Grup biletleri için bir sorgu nasıl görünür? Teşekkürler.
paulkon

4
Grup ve Kullanıcı tablolarında sürekli hesaplanan sütunların faydası nedir? Grup tablosundaki birincil anahtar zaten Grup Kimlikleri ve Kullanıcı Kimlikleri arasında hiçbir örtüşme olmayacağını garanti eder, bu nedenle yabancı anahtarın yalnızca Parti Kimliği üzerinde olması gerekir. Yazılan herhangi bir sorgu yine de PartyTypeName'deki tabloları bilmeye ihtiyaç duyacaktır.
Arin Taylor

1
@ArinTaylor kalıcı sütun, Kullanıcı tipi bir Taraf oluşturmamızı ve bunu dbo.Group'taki bir kayıtla ilişkilendirmemizi engeller.
Nathan Skerl

3
@paulkon Bunun eski bir soru olduğunu biliyorum ama sorgu şöyle bir şey SELECT t.Subject AS ticketSubject, CASE WHEN u.Name IS NOT NULL THEN u.Name ELSE g.Name END AS ticketOwnerName FROM Ticket t INNER JOIN Party p ON t.Owner=p.PartyId LEFT OUTER JOIN User u ON u.ID=p.PartyId LEFT OUTER JOIN Group g on g.ID=p.PartyID;olurdu Sonuçta her bilet konusuna ve sahibinin adına sahip olursunuz.
Corey McMahon

2
4. seçenekle ilgili olarak, birisi bunun bir anti patern mi yoksa anti patern için bir çözüm mü olduğunu doğrulayabilir mi?
inckka

31

@Nathan Skerl'in listesindeki ilk seçenek, daha önce çalıştığım, üç tablo arasında benzer bir ilişki kurulduğu bir projede uygulanan şeydi. (Biri, birer birer diğer ikisine başvurdu.)

Dolayısıyla, başvuru tablosunda iki yabancı anahtar sütunu vardı ve ayrıca tek bir satırla tam olarak bir tabloya (ikisi de değil, ikisi de değil) başvurulmasını garanti etmek için bir kısıtlaması vardı.

Tablolarınıza uygulandığında şöyle görünebilir:

CREATE TABLE dbo.[Group]
(
    ID int NOT NULL CONSTRAINT PK_Group PRIMARY KEY,
    Name varchar(50) NOT NULL
);

CREATE TABLE dbo.[User]
(
    ID int NOT NULL CONSTRAINT PK_User PRIMARY KEY,
    Name varchar(50) NOT NULL
);

CREATE TABLE dbo.Ticket
(
    ID int NOT NULL CONSTRAINT PK_Ticket PRIMARY KEY,
    OwnerGroup int NULL
      CONSTRAINT FK_Ticket_Group FOREIGN KEY REFERENCES dbo.[Group] (ID),
    OwnerUser int NULL
      CONSTRAINT FK_Ticket_User  FOREIGN KEY REFERENCES dbo.[User]  (ID),
    Subject varchar(50) NULL,
    CONSTRAINT CK_Ticket_GroupUser CHECK (
      CASE WHEN OwnerGroup IS NULL THEN 0 ELSE 1 END +
      CASE WHEN OwnerUser  IS NULL THEN 0 ELSE 1 END = 1
    )
);

Gördüğünüz gibi, Tickettablonun iki sütunu vardır OwnerGroupve OwnerUserher ikisi de boş değer atanabilir yabancı anahtarlardır. (Diğer iki tablodaki ilgili sütunlar buna göre birincil anahtarlar yapılır.) CK_Ticket_GroupUserKontrol kısıtlaması, iki yabancı anahtar sütunundan yalnızca birinin bir başvuru içermesini sağlar (diğeri NULL, bu nedenle her ikisinin de boş değer atanabilir olması gerekir).

( Ticket.IDBu özel uygulama için birincil anahtar gerekli değildir, ancak böyle bir tabloda birinin olması kesinlikle zarar vermez.)


1
Yazılımımızda da bu var ve genel bir veri erişim çerçevesi oluşturmaya çalışıyorsanız bundan kaçınırım. Bu tasarım, uygulama katmanındaki karmaşıklığı artıracaktır.
Frank.Germain

4
SQL'de gerçekten yeniyim, bu yüzden bu yanlışsa beni düzeltin, ancak bu tasarım, yalnızca iki tür bilet sahibine ihtiyacınız olacağından son derece emin olduğunuzda kullanılacak bir yaklaşım gibi görünüyor. Yolun sonunda, üçüncü bir bilet sahibi türü tanıtılmışsa, tabloya üçüncü bir boş değer atanabilir yabancı anahtar sütunu eklemeniz gerekir.
Shadoninja

@Shadoninja: Yanlış değilsin. Aslında, bence bu tamamen adil bir açıklama yolu. Genelde haklı olduğu durumlarda bu tür bir çözüme itiraz ediyorum, ancak seçenekleri değerlendirirken kesinlikle aklıma ilk gelmez - tam da ana hatlarıyla belirttiğiniz nedenden dolayı.
Andriy M

2
@ Frank.Germain Bu durumda iki sütun dayalı benzersiz bir yabancı tuşunu kullanabilirsiniz RefID, hedef tablonun sabit kimliğidir. Bütünlüğe ihtiyacınız varsa tetikleyici veya uygulama katmanında kontroller yapabilirsiniz. Bu durumda genel erişim mümkündür. SQL, FK'nin bu şekilde tanımlanmasına izin vererek hayatımızı kolaylaştırmalıdır. RefTypeRefType
djmj

2

Yine başka bir seçenek, Ticketsahip varlık türünü ( Userveya Group) belirten bir sütunda, referanslı Userveya Groupid ile ikinci sütuna sahip olmak ve Yabancı Anahtarları KULLANMAMAK, bunun yerine bilgi bütünlüğünü uygulamak için bir Tetikleyiciye güvenmektir.

Nathan'ın mükemmel modeline göre burada gördüğüm iki avantaj (yukarıda):

  • Daha hızlı netlik ve basitlik.
  • Yazmak için daha basit sorgular.

1
Ama bu yabancı anahtara izin vermez, değil mi? Halen şu anki projem için doğru tasarımı bulmaya çalışıyorum, burada bir tablo gelecekte en az 3 belki daha fazla
olabilir

2

Diğer bir yaklaşım, her potansiyel kaynak türü için sütunlar içeren bir ilişkilendirme tablosu oluşturmaktır. Örneğinizde, mevcut iki sahip türünün her birinin kendi tablosu vardır (bu, başvurmanız gereken bir şey olduğu anlamına gelir). Bu her zaman böyle olacaksa, böyle bir şeye sahip olabilirsiniz:

CREATE TABLE dbo.Group
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)  

CREATE TABLE dbo.User
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)

CREATE TABLE dbo.Ticket
(
    ID int NOT NULL,
    Owner_ID int NOT NULL,
    Subject varchar(50) NULL
)

CREATE TABLE dbo.Owner
(
    ID int NOT NULL,
    User_ID int NULL,
    Group_ID int NULL,
    {{AdditionalEntity_ID}} int NOT NULL
)

Bu çözümle, veritabanına yeni varlıklar eklerken yeni sütunlar eklemeye devam edeceksiniz ve @ Nathan Skerl tarafından gösterilen yabancı anahtar kısıtlama modelini silip yeniden oluşturacaksınız. Bu çözüm @Nathan Skerl'e çok benziyor ancak farklı görünüyor (tercihe göre).

Her yeni Sahip türü için yeni bir Tablonuz olmayacaksa, her bir potansiyel Sahip için yabancı anahtar sütunu yerine bir owner_type eklemeniz iyi olabilir:

CREATE TABLE dbo.Group
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)  

CREATE TABLE dbo.User
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)

CREATE TABLE dbo.Ticket
(
    ID int NOT NULL,
    Owner_ID int NOT NULL,
    Owner_Type string NOT NULL, -- In our example, this would be "User" or "Group"
    Subject varchar(50) NULL
)

Yukarıdaki yöntemle, istediğiniz kadar Sahip Türü ekleyebilirsiniz. Owner_ID, yabancı anahtar kısıtlamasına sahip olmayacak, ancak diğer tablolara referans olarak kullanılacaktır. Dezavantajı, şemaya göre hemen belli olmadığından, sahiplerin orada ne türlerinin olduğunu görmek için tabloya bakmanız gerekmesidir. Bunu yalnızca sahip türlerini önceden bilmiyorsanız ve diğer tablolarla bağlantı kurmayacaklarsa öneririm. Sahip tiplerini önceden biliyorsanız, @Nathan Skerl gibi bir çözüme başvururum.

Bazı SQL'i yanlış anladıysam özür dilerim, bunu birlikte attım.


-4
CREATE TABLE dbo.OwnerType
(
    ID int NOT NULL,
    Name varchar(50) NULL
)

insert into OwnerType (Name) values ('User');
insert into OwnerType (Name) values ('Group');

Bunun bir bayrak kullanmak yerine istediğinizi temsil etmenin en genel yolu olacağını düşünüyorum.

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.