Kunci Asing ke beberapa tabel

127

Saya punya 3 tabel yang relevan di database saya.

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
)

Pengguna termasuk dalam beberapa grup. Ini dilakukan melalui hubungan banyak ke banyak, tetapi tidak relevan dalam kasus ini. Tiket dapat dimiliki oleh grup atau pengguna, melalui kolom dbo.Ticket.Owner.

Cara apa yang PALING BENAR menggambarkan hubungan antara tiket dan secara opsional pengguna atau grup?

Saya berpikir bahwa saya harus menambahkan bendera di tabel tiket yang mengatakan jenis apa yang memilikinya.

Darthg8r
sumber
Menurut saya, setiap tiket dimiliki oleh grup. Hanya saja pengguna adalah satu grup. Pilihan mana 4 dari model @ nathan-skerl. Jika Anda menggunakan Panduan sebagai kunci, semuanya juga berfungsi dengan baik
GraemeMiller

Jawaban:

149

Anda memiliki beberapa pilihan, semuanya bervariasi dalam "kebenaran" dan kemudahan penggunaan. Seperti biasa, desain yang tepat bergantung pada kebutuhan Anda.

  • Anda cukup membuat dua kolom di Ticket, OwnedByUserId dan OwnedByGroupId, dan memiliki Kunci Asing nullable untuk setiap tabel.

  • Anda dapat membuat tabel referensi M: M yang mengaktifkan hubungan tiket: pengguna dan tiket: grup. Mungkin di masa mendatang Anda ingin mengizinkan satu tiket dimiliki oleh banyak pengguna atau grup? Desain ini tidak memaksakan bahwa tiket harus dimiliki oleh satu entitas saja.

  • Anda dapat membuat grup default untuk setiap pengguna dan memiliki tiket yang hanya dimiliki oleh Grup sebenarnya atau Grup default Pengguna.

  • Atau (pilihan saya) membuat model entitas yang bertindak sebagai basis untuk Pengguna dan Grup, dan memiliki tiket yang dimiliki oleh entitas tersebut.

Berikut adalah contoh kasar menggunakan skema yang Anda posting:

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
)
Nathan Skerl
sumber
7
Seperti apa kueri tiket Pengguna / Grup? Terima kasih.
paulkon
4
Apa manfaat dari kolom terhitung yang bertahan di tabel Grup dan Pengguna? Kunci utama dalam tabel Partai sudah memastikan bahwa tidak akan ada tumpang tindih di ID Grup dan ID Pengguna, jadi kunci asing hanya perlu berada di PartyId saja. Kueri apa pun yang ditulis masih perlu mengetahui tabel dari PartyTypeName.
Arin Taylor
1
@ArinTaylor kolom persisten mencegah kita dari membuat Kelompok Pengguna tipe dan menghubungkannya dengan catatan di dbo.Group.
Nathan Skerl
3
@paulkon Saya tahu ini adalah pertanyaan lama tetapi pertanyaannya akan seperti ini 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;. Hasilnya, Anda akan memiliki semua subjek tiket dan nama pemilik.
Corey McMahon
2
Mengenai opsi 4, Dapatkah seseorang memastikan apakah ini anti pola atau solusi untuk anti pola?
inckka
31

Opsi pertama dalam daftar @Nathan Skerl adalah apa yang diimplementasikan dalam proyek yang pernah saya tangani, di mana hubungan serupa dibuat antara tiga tabel. (Salah satunya mereferensikan dua lainnya, satu per satu.)

Jadi, tabel referensi memiliki dua kolom kunci asing, dan juga memiliki batasan untuk menjamin bahwa tepat satu tabel (tidak keduanya, tidak keduanya) direferensikan oleh satu baris.

Berikut tampilannya saat diterapkan ke tabel Anda:

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
    )
);

Seperti yang Anda lihat, Tickettabel memiliki dua kolom, OwnerGroupdan OwnerUser, keduanya merupakan kunci asing yang dapat dinihilkan. (Kolom masing-masing di dua tabel lainnya dibuat sebagai kunci utama.) CK_Ticket_GroupUserBatasan pemeriksaan memastikan bahwa hanya satu dari dua kolom kunci asing yang berisi referensi (yang lainnya adalah NULL, itulah mengapa keduanya harus nullable).

(Kunci utama pada Ticket.IDtidak diperlukan untuk penerapan khusus ini, tetapi pasti tidak ada salahnya untuk memilikinya di tabel seperti ini.)

Andriy M
sumber
1
Ini juga yang kami miliki di perangkat lunak kami dan saya akan menghindarinya jika Anda mencoba membuat kerangka kerja akses data generik. Desain ini akan meningkatkan kompleksitas pada lapisan aplikasi.
Frank. Germain
4
Saya benar-benar baru mengenal SQL jadi perbaiki saya jika ini salah, tetapi desain ini tampaknya menjadi pendekatan untuk digunakan ketika Anda sangat yakin bahwa Anda hanya memerlukan dua jenis pemilik tiket. Di masa mendatang jika tipe pemilik tiket ketiga diperkenalkan, Anda harus menambahkan kolom kunci asing nullable ketiga ke tabel.
Shadoninja
@Shadoninja: Anda tidak salah. Faktanya, saya pikir itu cara yang sangat adil untuk menggambarkannya. Saya umumnya baik-baik saja dengan solusi semacam ini yang dibenarkan, tetapi tentu saja ini tidak akan menjadi yang pertama dalam pikiran saya ketika mempertimbangkan opsi - justru karena alasan yang telah Anda uraikan.
Andriy M
2
@ Frank.Germain Dalam hal ini Anda dapat menggunakan kunci asing yang unik didasarkan pada dua kolom RefID, RefTypedi mana RefTypemerupakan identifier tetap tabel target. Jika Anda membutuhkan integritas, Anda dapat melakukan pemeriksaan di pemicu atau lapisan aplikasi. Pengambilan generik dimungkinkan dalam kasus ini. SQL harus mengizinkan definisi FK seperti ini, membuat hidup kita lebih mudah.
djmj
2

Namun opsi lain adalah memiliki, di Ticket, satu kolom yang menentukan jenis entitas pemilik ( Useratau Group), kolom kedua dengan referensi Useratau Groupid dan TIDAK menggunakan Kunci Asing melainkan mengandalkan Pemicu untuk menegakkan integritas referensial.

Dua keunggulan yang saya lihat di sini dibandingkan model Nathan yang luar biasa (di atas):

  • Kejelasan dan kesederhanaan yang lebih langsung.
  • Kueri yang lebih sederhana untuk ditulis.
Jan Żankowski
sumber
1
Tapi ini tidak akan mengizinkan kunci asing, kan? Saya masih mencoba mencari desain yang tepat untuk proyek saya saat ini, di mana satu tabel dapat mereferensikan setidaknya 3 mungkin lebih di masa depan
Can Rau
2

Pendekatan lain adalah membuat tabel asosiasi yang berisi kolom untuk setiap jenis sumber daya potensial. Dalam contoh Anda, masing-masing dari dua tipe pemilik yang ada memiliki tabelnya sendiri (yang berarti Anda memiliki sesuatu untuk dirujuk). Jika ini akan selalu terjadi, Anda dapat memiliki sesuatu seperti ini:

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
)

Dengan solusi ini, Anda akan terus menambahkan kolom baru saat Anda menambahkan entitas baru ke database dan Anda akan menghapus serta membuat ulang pola batasan kunci asing yang ditunjukkan oleh @Nathan Skerl. Solusi ini sangat mirip dengan @Nathan Skerl tetapi terlihat berbeda (tergantung preferensi).

Jika Anda tidak akan memiliki Tabel baru untuk setiap jenis Pemilik baru, maka mungkin akan lebih baik untuk menyertakan tipe_milik daripada kolom kunci asing untuk setiap Pemilik potensial:

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
)

Dengan metode di atas, Anda dapat menambahkan Jenis Pemilik sebanyak yang Anda inginkan. Owner_ID tidak akan memiliki batasan kunci asing tetapi akan digunakan sebagai referensi ke tabel lain. Kelemahannya adalah Anda harus melihat tabel untuk melihat apa tipe pemiliknya karena tidak segera terlihat berdasarkan skema. Saya hanya akan menyarankan ini jika Anda tidak mengetahui jenis pemilik sebelumnya dan mereka tidak akan ditautkan ke tabel lain. Jika Anda mengetahui tipe pemilik sebelumnya, saya akan menggunakan solusi seperti @Nathan Skerl.

Maaf jika saya salah SQL, saya baru saja menyatukannya.

smoosh911
sumber
-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');

Saya pikir itu akan menjadi cara paling umum untuk mewakili apa yang Anda inginkan daripada menggunakan sebuah bendera.

Francisco Soto
sumber