कई तालिकाओं के लिए विदेशी कुंजी


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 पसंद करते हैं। यदि आप Guids का उपयोग कुंजियों के रूप में करते हैं तो पूरी चीज भी काफी अच्छी तरह से काम करती है
ग्रीममेइलर

जवाबों:


149

आपके पास कुछ विकल्प हैं, सभी "शुद्धता" और उपयोग में आसानी में भिन्न हैं। हमेशा की तरह, सही डिजाइन आपकी आवश्यकताओं पर निर्भर करता है।

  • आप बस टिकट में दो कॉलम बना सकते हैं, OwnedByUserId और OwnedByGroupId, और प्रत्येक तालिका के लिए अशक्त विदेशी कुंजी है।

  • आप M: M संदर्भ सारणी बना सकते हैं, जिसमें टिकट और उपयोगकर्ता दोनों के टिकट हो सकते हैं। शायद भविष्य में आप एक ही टिकट को कई उपयोगकर्ताओं या समूहों के स्वामित्व की अनुमति देना चाहेंगे? यह डिज़ाइन लागू नहीं करता है कि एक टिकट केवल एक इकाई के स्वामित्व में होना चाहिए।

  • आप प्रत्येक उपयोगकर्ता के लिए एक डिफ़ॉल्ट समूह बना सकते हैं और टिकट केवल एक सच्चे समूह या उपयोगकर्ता के डिफ़ॉल्ट समूह के स्वामित्व में हो सकते हैं।

  • या (मेरी पसंद) एक इकाई है जो उपयोगकर्ताओं और समूहों दोनों के लिए आधार के रूप में कार्य करती है, और उस इकाई के स्वामित्व वाले टिकट हैं।

आपके पोस्ट किए गए स्कीमा का उपयोग करके एक मोटा उदाहरण

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
उपयोगकर्ता / समूह टिकट के लिए एक क्वेरी क्या दिखती है? धन्यवाद।
पॉलकॉन

4
समूह और उपयोगकर्ता तालिकाओं में लगातार गणना किए गए स्तंभों का क्या लाभ है? पार्टी तालिका में प्राथमिक कुंजी पहले से ही यह सुनिश्चित करती है कि समूह Ids और उपयोगकर्ता Ids में कोई ओवरलैप नहीं होगा, इसलिए विदेशी कुंजी को केवल पार्टीआईड पर ही होना चाहिए। लिखे गए किसी भी प्रश्न को अभी भी वैसे भी पार्टीटेपनेम से तालिकाओं को जानना होगा।
टेलर

1
@ArinTaylor लगातार कॉलम हमें एक प्रकार की उपयोगकर्ता की पार्टी बनाने से रोकता है और इसे dbo.Group में रिकॉर्ड से संबंधित करता है।
नाथन स्कर्ल

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;कि आपके पास हर टिकट विषय और मालिक का नाम होगा।
कोरी मैकमोहन

2
विकल्प 4 के बारे में, क्या कोई पुष्टि कर सकता है कि यह एक विरोधी पैटर्न है या एक विरोधी पैटर्न का समाधान है?
इंकाका

31

@ नातान स्कर्ल की सूची में पहला विकल्प वह है जिसे मैंने एक बार एक परियोजना में लागू किया था, जहां तीन तालिकाओं के बीच एक समान संबंध स्थापित किया गया था। (उनमें से एक ने दो अन्य को संदर्भित किया, एक समय में।)

इसलिए, संदर्भित तालिका में दो विदेशी कुंजी कॉलम थे, और यह भी एक बाधा की गारंटी थी कि वास्तव में एक तालिका (दोनों नहीं, दोनों नहीं) को एक पंक्ति द्वारा संदर्भित किया गया था।

अपनी तालिकाओं पर लागू होने पर यह कैसा दिख सकता है:

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चेक बाधा सुनिश्चित करती है कि दो विदेशी कुंजी कॉलमों में से एक में एक संदर्भ (दूसरा NULL हो, इसीलिए दोनों को अशक्त होना है)।

( Ticket.IDइस विशेष कार्यान्वयन के लिए प्राथमिक कुंजी आवश्यक नहीं है, लेकिन यह निश्चित रूप से इस तरह से तालिका में एक को नुकसान नहीं पहुंचाएगा।)


1
हमारे सॉफ्टवेयर में भी यही है और अगर आप जेनेरिक डेटा एक्सेस फ्रेमवर्क बनाने की कोशिश कर रहे हैं तो मैं इससे बचूंगा। यह डिज़ाइन ऐप लेयर में जटिलता बढ़ाएगा।
फ्रैंक.गर्मैन

4
मैं एसक्यूएल के लिए वास्तव में नया हूं इसलिए अगर यह गलत है तो मुझे सही करें, लेकिन यह डिजाइन उपयोग करने के लिए एक दृष्टिकोण प्रतीत होता है जब आप बेहद आश्वस्त होते हैं कि आपको केवल दो मालिक प्रकार के टिकट की आवश्यकता होगी। सड़क के नीचे यदि तीसरा टिकट स्वामी प्रकार पेश किया गया था, तो आपको तालिका में एक तीसरा अशक्त विदेशी कुंजी कॉलम जोड़ना होगा।
Shadoninja

@ शादोनिन्जा: आप गलत नहीं हैं। वास्तव में, मुझे लगता है कि इसे लगाने का पूरी तरह से उचित तरीका है। मैं आम तौर पर इस तरह के समाधान के साथ ठीक हूं जहां यह उचित है, लेकिन विकल्पों पर विचार करते समय यह निश्चित रूप से मेरे दिमाग में नहीं होगा - ठीक है क्योंकि आपके द्वारा बताए गए कारण के कारण।
एंड्री एम

2
@ Frank.Germain इस मामले में आप दो स्तंभों के आधार पर एक अद्वितीय विदेशी कुंजी का उपयोग कर सकते हैं RefID, RefTypeजहां RefTypeलक्ष्य तालिका का एक निश्चित पहचानकर्ता है। यदि आपको अखंडता की आवश्यकता है तो आप ट्रिगर या ऐप लेयर में जांच कर सकते हैं। इस मामले में सामान्य पुनर्प्राप्ति संभव है। SQL को FK की परिभाषा इस तरह की अनुमति देनी चाहिए, जिससे हमारा जीवन आसान हो सके।
djmj

2

फिर भी एक अन्य विकल्प है Ticket, एक स्तंभ, जो अनुवर्ती इकाई प्रकार ( Userया Group) को निर्दिष्ट करता है , संदर्भित Userया Groupआईडी के साथ दूसरा कॉलम और विदेशी कुंजियों का उपयोग करने के लिए नहीं, बल्कि इसके बजाय संदर्भात्मक अखंडता को लागू करने के लिए ट्रिगर पर भरोसा करते हैं।

नाथन के उत्कृष्ट मॉडल (ऊपर) में मैं यहां दो फायदे देख रहा हूं :

  • अधिक तत्काल स्पष्टता और सरलता।
  • लिखने के लिए सरल प्रश्न।

1
लेकिन यह एक विदेशी कुंजी के अधिकार के लिए अनुमति नहीं देगा? मैं अभी भी अपने मौजूदा प्रोजेक्ट के लिए सही डिज़ाइन का पता लगाने की कोशिश कर रहा हूं, जहां एक टेबल भविष्य में कम से कम 3 और अधिक का संदर्भ दे
सकती है

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 से काफी मिलता-जुलता है, लेकिन भिन्न (वरीयता के लिए) दिखता है।

यदि आपके पास प्रत्येक नए स्वामी प्रकार के लिए एक नई तालिका नहीं है, तो शायद प्रत्येक संभावित स्वामी के लिए एक विदेशी कुंजी कॉलम के बजाय एक मालिक_प्रकार शामिल करना अच्छा होगा:

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 में एक विदेशी कुंजी बाधा नहीं होगी, लेकिन इसे अन्य तालिकाओं के संदर्भ के रूप में उपयोग किया जाएगा। नकारात्मक पक्ष यह है कि आपको यह देखने के लिए तालिका को देखना होगा कि मालिक के प्रकार क्या हैं क्योंकि यह स्कीमा के आधार पर तुरंत स्पष्ट नहीं है। मैं केवल यह सुझाव दूंगा कि यदि आप पहले से मालिक के प्रकार को नहीं जानते हैं और वे अन्य तालिकाओं से नहीं जुड़ेंगे। यदि आप पहले से मालिक प्रकार जानते हैं, तो मैं @Nanan 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');

मुझे लगता है कि एक ध्वज का उपयोग करने के बजाय जो आप चाहते हैं उसका प्रतिनिधित्व करने का सबसे सामान्य तरीका होगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.