Khóa ngoại cho nhiều bảng


127

Tôi đã có 3 bảng có liên quan trong cơ sở dữ liệu của mình.

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
)

Người dùng thuộc nhiều nhóm. Điều này được thực hiện thông qua nhiều mối quan hệ, nhưng không liên quan trong trường hợp này. Một vé có thể được sở hữu bởi một nhóm hoặc một người dùng, thông qua trường dbo.Ticket.Owner.

Điều gì sẽ là cách ĐÚNG NHẤT mô tả mối quan hệ này giữa một vé và tùy chọn người dùng hoặc một nhóm?

Tôi nghĩ rằng tôi nên thêm một lá cờ trong bảng vé cho biết loại nào sở hữu nó.


Theo tôi, mỗi vé đều thuộc sở hữu của một nhóm. Nó chỉ là một người dùng là một nhóm của một. Lựa chọn 4 từ các mô hình @ nathan-skerl. Nếu bạn sử dụng Hướng dẫn làm khóa, toàn bộ hoạt động cũng hoạt động khá tốt
GraemeMiller

Câu trả lời:


149

Bạn có một vài lựa chọn, tất cả đều khác nhau về "tính chính xác" và dễ sử dụng. Như mọi khi, thiết kế phù hợp phụ thuộc vào nhu cầu của bạn.

  • Bạn chỉ có thể tạo hai cột trong Ticket, OwnedByUserId và OwnedBygroupId và có các khóa ngoại không thể rỗng cho mỗi bảng.

  • Bạn có thể tạo các bảng tham chiếu M: M cho phép cả mối quan hệ giữa vé: người dùng và vé: nhóm. Có lẽ trong tương lai bạn sẽ muốn cho phép một vé duy nhất được sở hữu bởi nhiều người dùng hoặc nhóm? Thiết kế này không bắt buộc rằng một vé phải được sở hữu bởi một thực thể duy nhất.

  • Bạn có thể tạo một nhóm mặc định cho mọi người dùng và có vé được sở hữu đơn giản bởi Nhóm thực sự hoặc Nhóm mặc định của Người dùng.

  • Hoặc (lựa chọn của tôi) mô hình một thực thể hoạt động như một cơ sở cho cả Người dùng và Nhóm và có vé thuộc sở hữu của thực thể đó.

Đây là một ví dụ sơ bộ sử dụng lược đồ được đăng của bạn:

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
Một truy vấn cho vé Người dùng / Nhóm sẽ như thế nào? Cảm ơn.
paulkon

4
Lợi ích của các cột được tính toán bền vững trong các bảng Nhóm và Người dùng là gì? Khóa chính trong bảng Party đã đảm bảo rằng sẽ không có sự trùng lặp trong Id nhóm và Id người dùng, do đó, khóa ngoại chỉ cần ở riêng PartyId. Bất kỳ truy vấn nào được viết vẫn sẽ cần biết các bảng từ PartyTypeName.
Arin Taylor

1
@ArinTaylor cột kiên trì ngăn chúng tôi tạo một Đảng loại Người dùng và liên quan đến một bản ghi trong dbo.group.
Nathan Skerl

3
@paulkon Tôi biết đây là một câu hỏi cũ nhưng truy vấn sẽ giống như 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;trong kết quả, bạn sẽ có mọi chủ đề vé và tên chủ sở hữu.
Corey McMahon

2
Liên quan đến tùy chọn 4, ai đó có thể xác nhận xem đây là mẫu chống hoặc giải pháp cho mẫu chống không?
inckka

31

Tùy chọn đầu tiên trong danh sách của @Nathan Skerl là những gì đã được thực hiện trong một dự án tôi từng làm việc cùng, nơi một mối quan hệ tương tự được thiết lập giữa ba bảng. (Một trong số họ tham chiếu hai người khác, từng người một.)

Vì vậy, bảng tham chiếu có hai cột khóa ngoài và cũng có một ràng buộc để đảm bảo rằng chính xác một bảng (không phải cả hai, không phải cả hai) được tham chiếu bởi một hàng.

Đây là cách nó có thể trông khi được áp dụng cho các bảng của bạn:

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

Như bạn có thể thấy, Ticketbảng có hai cột OwnerGroupOwnerUsercả hai đều là khóa ngoại không thể rỗng. (Các cột tương ứng trong hai bảng khác được tạo các khóa chính tương ứng.) CK_Ticket_GroupUserRàng buộc kiểm tra đảm bảo rằng chỉ một trong hai cột khóa ngoại có chứa một tham chiếu (cái còn lại là NULL, đó là lý do tại sao cả hai phải là null).

(Khóa chính Ticket.IDkhông cần thiết cho việc triển khai cụ thể này, nhưng chắc chắn sẽ không có hại khi có một cái trong bảng như thế này.)


1
Đây cũng là những gì chúng tôi có trong phần mềm của mình và tôi sẽ tránh nếu bạn đang cố gắng tạo một khung truy cập dữ liệu chung. Thiết kế này sẽ tăng độ phức tạp trong lớp ứng dụng.
Frank.Germain

4
Tôi thực sự chưa quen với SQL vì vậy hãy sửa cho tôi nếu điều này sai, nhưng thiết kế này dường như là một cách tiếp cận để sử dụng khi bạn cực kỳ tự tin rằng bạn sẽ chỉ cần hai loại chủ sở hữu của một vé. Xuống đường nếu loại chủ sở hữu vé thứ ba được giới thiệu, bạn sẽ phải thêm một cột khóa ngoại không thể thứ ba vào bảng.
Shadoninja

@Shadoninja: Bạn không sai. Trên thực tế, tôi nghĩ đó là một cách hoàn toàn công bằng để đặt nó. Tôi thường ổn với loại giải pháp này khi nó hợp lý, nhưng chắc chắn nó sẽ không phải là đầu tiên trong tâm trí của tôi khi xem xét các lựa chọn - chính xác là vì lý do bạn đã vạch ra.
Andriy M

2
@ Frank.Germain Trong trường hợp này, bạn có thể sử dụng khóa ngoại duy nhất dựa trên hai cột RefID, RefTypetrong đó RefTypelà một định danh cố định của bảng đích. Nếu bạn cần tính toàn vẹn, bạn có thể thực hiện kiểm tra trong lớp kích hoạt hoặc ứng dụng. Có thể truy xuất chung trong trường hợp này. SQL nên cho phép định nghĩa FK như thế này, làm cho cuộc sống của chúng ta dễ dàng hơn.
djmj

2

Tuy nhiên, một tùy chọn khác là có Ticketmột cột xác định loại thực thể sở hữu ( Userhoặc Group), cột thứ hai có tham chiếu Userhoặc Groupid và KHÔNG sử dụng Khóa ngoài mà thay vào đó dựa vào Kích hoạt để thực thi tính toàn vẹn tham chiếu.

Hai lợi thế tôi thấy ở đây so với mô hình xuất sắc của Nathan (ở trên):

  • Rõ ràng hơn và đơn giản hơn.
  • Truy vấn đơn giản hơn để viết.

1
Nhưng điều này sẽ không cho phép một khóa ngoại phải không? Tôi vẫn đang cố gắng tìm ra thiết kế phù hợp cho dự án hiện tại của mình, trong đó một bảng có thể tham chiếu ít nhất 3 có thể nhiều hơn trong tương lai
Can Rau

2

Một cách tiếp cận khác là tạo một bảng kết hợp có chứa các cột cho từng loại tài nguyên tiềm năng. Trong ví dụ của bạn, mỗi loại trong số hai loại chủ sở hữu hiện tại có bảng riêng (có nghĩa là bạn có thứ gì đó để tham khảo). Nếu đây sẽ luôn là trường hợp bạn có thể có một cái gì đó như thế này:

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
)

Với giải pháp này, bạn sẽ tiếp tục thêm các cột mới khi bạn thêm các thực thể mới vào cơ sở dữ liệu và bạn sẽ xóa và tạo lại mẫu ràng buộc khóa ngoại được hiển thị bởi @Nathan Skerl. Giải pháp này rất giống với @Nathan Skerl nhưng trông khác biệt (tùy theo sở thích).

Nếu bạn sẽ không có Bảng mới cho mỗi loại Chủ sở hữu mới thì có lẽ nên bao gồm một chủ sở hữu_type thay vì một cột khóa ngoại cho mỗi Chủ sở hữu tiềm năng:

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
)

Với phương pháp trên, bạn có thể thêm bao nhiêu Chủ sở hữu tùy thích. Chủ sở hữu_ID sẽ không có ràng buộc khóa ngoại nhưng sẽ được sử dụng làm tham chiếu cho các bảng khác. Nhược điểm là bạn sẽ phải nhìn vào bảng để xem chủ sở hữu loại gì vì nó không rõ ràng ngay lập tức dựa trên lược đồ. Tôi sẽ chỉ đề xuất điều này nếu bạn không biết trước các loại chủ sở hữu và họ sẽ không liên kết với các bảng khác. Nếu bạn biết trước các loại chủ sở hữu, tôi sẽ đi với một giải pháp như @Nathan Skerl.

Xin lỗi nếu tôi có một số SQL sai, tôi đã ném nó cùng nhau.


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

Tôi nghĩ đó sẽ là cách tổng quát nhất để thể hiện những gì bạn muốn thay vì sử dụng cờ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.