Foreign Key ไปยังหลาย ๆ ตาราง


127

ฉันมี 3 ตารางที่เกี่ยวข้องในฐานข้อมูลของฉัน

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
)

ผู้ใช้อยู่ในหลายกลุ่ม สิ่งนี้ทำได้ผ่านความสัมพันธ์แบบกลุ่มต่อกลุ่มมาก แต่ไม่เกี่ยวข้องในกรณีนี้ ตั๋วสามารถเป็นเจ้าของได้ทั้งกลุ่มหรือผู้ใช้ผ่านช่อง dbo.Ticket.Owner

อะไรคือวิธีที่ถูกต้องที่สุดในการอธิบายความสัมพันธ์ระหว่างตั๋วกับผู้ใช้หรือกลุ่มทางเลือก

ฉันคิดว่าควรเพิ่มธงในตารางตั๋วที่ระบุว่าประเภทใดเป็นเจ้าของ


ในใจของฉันตั๋วทุกใบเป็นของกลุ่ม เป็นเพียงการที่ผู้ใช้เป็นกลุ่มหนึ่ง ตัวเลือกใด 4 จากโมเดล @ nathan-skerl หากคุณใช้ Guids เป็นคีย์สิ่งทั้งหมดก็ใช้ได้ดีเช่นกัน
GraemeMiller

คำตอบ:


150

คุณมีทางเลือกสองสามทางซึ่งทั้งหมดนี้มีความ "ถูกต้อง" และความสะดวกในการใช้ เช่นเคยการออกแบบที่เหมาะสมขึ้นอยู่กับความต้องการของคุณ

  • คุณสามารถสร้างสองคอลัมน์ใน Ticket, OwnedByUserId และ OwnedByGroupId และมี Foreign Keys ที่เป็นโมฆะในแต่ละตารางได้

  • คุณสามารถสร้างตารางอ้างอิง M: M เพื่อเปิดใช้งานทั้ง ticket: user และ ticket: group relationship บางทีในอนาคตคุณอาจต้องการอนุญาตให้ตั๋วใบเดียวเป็นของผู้ใช้หลายคนหรือหลายกลุ่ม? การออกแบบนี้ไม่ได้บังคับว่าตั๋วจะต้องเป็นของเอนทิตีเดียวเท่านั้น

  • คุณสามารถสร้างกลุ่มเริ่มต้นสำหรับผู้ใช้ทุกคนและมีตั๋วที่เป็นของกลุ่มจริงหรือกลุ่มเริ่มต้นของผู้ใช้

  • หรือ (ทางเลือกของฉัน) สร้างโมเดลเอนทิตีที่ทำหน้าที่เป็นฐานสำหรับทั้งผู้ใช้และกลุ่มและมีตั๋วที่เป็นของเอนทิตีนั้น

นี่คือตัวอย่างคร่าวๆโดยใช้สคีมาที่คุณโพสต์ไว้:

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
คำถามสำหรับตั๋วผู้ใช้ / กลุ่มจะเป็นอย่างไร ขอบคุณ
paulkon

4
ประโยชน์ของคอลัมน์ที่คำนวณต่อเนื่องในตารางกลุ่มและผู้ใช้คืออะไร? คีย์หลักในตารางปาร์ตี้ทำให้มั่นใจได้ว่าจะไม่มีการทับซ้อนกันในรหัสกลุ่มและรหัสผู้ใช้ดังนั้นคีย์ต่างประเทศจึงต้องอยู่ใน PartyId เพียงอย่างเดียว คำถามใด ๆ ที่เขียนขึ้นก็ยังจำเป็นต้องทราบตารางจาก PartyTypeName อยู่ดี
Arin Taylor

1
@ArinTaylor คอลัมน์ที่ยังคงอยู่จะป้องกันไม่ให้เราสร้าง Party of type User และเกี่ยวข้องกับบันทึกใน dbo.Group
Nathan Skerl

3
@paulkon ฉันรู้ว่านี่เป็นคำถามเก่า แต่คำถามจะเป็นเช่น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;ในผลลัพธ์ที่คุณจะมีทุกเรื่องตั๋วและชื่อเจ้าของ
Corey McMahon

2
เกี่ยวกับตัวเลือกที่ 4 มีใครสามารถยืนยันได้หรือไม่ว่านี่เป็นรูปแบบการต่อต้านหรือวิธีแก้ปัญหาสำหรับรูปแบบการต่อต้านหรือไม่?
inckka

31

ตัวเลือกแรกในรายการของ@Nathan Skerlคือสิ่งที่ดำเนินการในโครงการที่ฉันเคยทำงานด้วยซึ่งมีการสร้างความสัมพันธ์ที่คล้ายกันระหว่างสามตาราง (หนึ่งในนั้นอ้างถึงอีกสองคนทีละคน)

ดังนั้นตารางอ้างอิงจึงมีคอลัมน์คีย์ต่างประเทศสองคอลัมน์และยังมีข้อ จำกัด ในการรับประกันว่าตารางเดียว (ไม่ใช่ทั้งคู่ไม่ใช่ทั้งคู่) ถูกอ้างอิงโดยแถวเดียว

นี่คือลักษณะที่ปรากฏเมื่อนำไปใช้กับตารางของคุณ:

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

ดังที่คุณเห็นTicketตารางมีสองคอลัมน์OwnerGroupและOwnerUserทั้งสองเป็นคีย์ต่างประเทศที่เป็นโมฆะ (คอลัมน์ที่เกี่ยวข้องในอีกสองตารางจะถูกสร้างเป็นคีย์หลักตามนั้น) CK_Ticket_GroupUserข้อ จำกัด ในการตรวจสอบจะทำให้แน่ใจว่ามีเพียงคอลัมน์คีย์ต่างประเทศหนึ่งในสองคอลัมน์เท่านั้นที่มีการอ้างอิง (อีกคอลัมน์หนึ่งเป็นโมฆะนั่นคือเหตุผลที่ทั้งสองต้องเป็นโมฆะ)

(คีย์หลักTicket.IDไม่จำเป็นสำหรับการใช้งานนี้ แต่แน่นอนว่าจะไม่เป็นอันตรายต่อการมีหนึ่งในตารางเช่นนี้)


1
นี่คือสิ่งที่เรามีในซอฟต์แวร์ของเราและฉันจะหลีกเลี่ยงหากคุณพยายามสร้างกรอบการเข้าถึงข้อมูลทั่วไป การออกแบบนี้จะเพิ่มความซับซ้อนในเลเยอร์แอป
Frank.Germain

4
ฉันยังใหม่กับ SQL มากดังนั้นโปรดแก้ไขฉันหากสิ่งนี้ผิด แต่การออกแบบนี้ดูเหมือนจะเป็นแนวทางที่จะใช้เมื่อคุณมั่นใจอย่างยิ่งว่าคุณจะต้องใช้ตั๋วประเภทเจ้าของเพียงสองประเภทเท่านั้น ไปตามถนนหากมีการแนะนำเจ้าของตั๋วประเภทที่สามคุณจะต้องเพิ่มคอลัมน์คีย์ต่างประเทศที่เป็นค่าว่างที่สามลงในตาราง
Shadoninja

@Shadoninja: คุณไม่ผิด อันที่จริงฉันคิดว่านั่นเป็นวิธีที่ยุติธรรมอย่างสมบูรณ์ โดยทั่วไปฉันโอเคกับวิธีการแก้ปัญหาแบบนี้ที่มีเหตุผล แต่แน่นอนว่ามันจะไม่เป็นอันดับแรกในใจของฉันเมื่อพิจารณาตัวเลือก - เนื่องจากเหตุผลที่คุณระบุไว้
Andriy M

2
@ Frank.Germain ในกรณีนี้คุณสามารถใช้คีย์ต่างประเทศที่ไม่ซ้ำกันขึ้นอยู่กับสองคอลัมน์RefID, RefTypeที่RefTypeเป็นรหัสคงที่ของตารางเป้าหมาย หากคุณต้องการความสมบูรณ์คุณสามารถตรวจสอบในทริกเกอร์หรือเลเยอร์แอพได้ การเรียกข้อมูลทั่วไปสามารถทำได้ในกรณีนี้ SQL ควรให้คำจำกัดความ FK เช่นนี้ทำให้ชีวิตของเราง่ายขึ้น
djmj

2

อีกทางเลือกหนึ่งคือมีในTicketหนึ่งคอลัมน์ระบุประเภทเอนทิตีที่เป็นเจ้าของ ( UserหรือGroup) คอลัมน์ที่สองที่มีการอ้างอิงUserหรือGroupรหัสและไม่ใช้ Foreign Keys แต่ใช้ Trigger เพื่อบังคับใช้ referential Integrity แทน

ข้อดีสองประการที่ฉันเห็นจากโมเดลที่ยอดเยี่ยมของนาธาน(ด้านบน):

  • ชัดเจนและเรียบง่ายมากขึ้นในทันที
  • ข้อความค้นหาที่ง่ายกว่าในการเขียน

1
แต่นี่ไม่ยอมให้มี Foreign Key ใช่ไหม ฉันยังคงพยายามหารูปแบบที่เหมาะสมสำหรับโครงการปัจจุบันของฉันโดยที่ตารางหนึ่งสามารถอ้างอิงได้อย่างน้อย 3 อย่างอาจจะมากกว่านั้นในอนาคต
Can Rau

2

อีกวิธีหนึ่งคือการสร้างตารางการเชื่อมโยงที่มีคอลัมน์สำหรับทรัพยากรที่มีศักยภาพแต่ละประเภท ในตัวอย่างของคุณเจ้าของที่มีอยู่ทั้งสองประเภทมีตารางของตัวเอง (ซึ่งหมายความว่าคุณมีสิ่งที่ต้องอ้างอิง) หากเป็นเช่นนี้เสมอคุณสามารถมีสิ่งนี้:

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
)

ด้วยโซลูชันนี้คุณจะยังคงเพิ่มคอลัมน์ใหม่ต่อไปเมื่อคุณเพิ่มเอนทิตีใหม่ลงในฐานข้อมูลและคุณจะลบและสร้างรูปแบบข้อ จำกัด คีย์ต่างประเทศที่แสดงโดย @Nathan Skerl โซลูชันนี้คล้ายกับ @Nathan Skerl มาก แต่ดูแตกต่างกัน (ขึ้นอยู่กับความชอบ)

หากคุณจะไม่มีตารางใหม่สำหรับ Owner ใหม่แต่ละประเภทอาจเป็นการดีที่จะรวม owner_type แทนคอลัมน์ Foreign Key สำหรับเจ้าของที่มีศักยภาพแต่ละราย:

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
)

ด้วยวิธีการข้างต้นคุณสามารถเพิ่มประเภทเจ้าของได้มากเท่าที่คุณต้องการ Owner_ID จะไม่มีข้อ จำกัด ของคีย์ภายนอก แต่จะใช้อ้างอิงไปยังตารางอื่น ๆ ข้อเสียคือคุณจะต้องดูตารางเพื่อดูว่าเจ้าของประเภทนั้นมีอะไรบ้างเนื่องจากไม่ชัดเจนในทันทีตามสคีมา ฉันจะแนะนำสิ่งนี้ก็ต่อเมื่อคุณไม่ทราบประเภทของเจ้าของล่วงหน้าและพวกเขาจะไม่เชื่อมโยงกับตารางอื่น ๆ ถ้าคุณรู้จักประเภทของเจ้าของมาก่อนฉันจะใช้วิธีแก้ปัญหาเช่น @Nathan Skerl

ขออภัยถ้าฉันมี SQL ผิดพลาดฉันเพิ่งโยนมันเข้าด้วยกัน


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

ฉันคิดว่านั่นเป็นวิธีทั่วไปที่สุดในการแสดงสิ่งที่คุณต้องการแทนที่จะใช้ธง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.