여러 테이블에 대한 외래 키


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 필드를 통해 그룹이나 사용자가 소유 할 수 있습니다.

티켓과 선택적으로 사용자 또는 그룹 간의이 관계를 설명 하는 가장 정확한 방법은 무엇입니까 ?

티켓 테이블에 어떤 유형이 소유하는지 알려주는 플래그를 추가해야한다고 생각합니다.


내 생각에 모든 티켓은 그룹 소유입니다. 사용자는 하나의 그룹입니다. @ nathan-skerl 모델의 선택 4. 당신 키로 GUID를 사용하는 경우 모든 일도 꽤 잘 작동
GraemeMiller

답변:


149

몇 가지 옵션이 있으며 모두 "정확성"과 사용 용이성이 다릅니다. 항상 그렇듯이 올바른 디자인은 필요에 따라 다릅니다.

  • Ticket에 OwnedByUserId와 OwnedByGroupId라는 두 개의 열을 만들고 각 테이블에 대해 nullable 외래 키를 가질 수 있습니다.

  • ticket : user 및 ticket : group 관계를 모두 활성화하는 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
사용자 / 그룹 티켓에 대한 쿼리는 어떻게 생겼습니까? 감사.
paulkon

4
Group 및 User 테이블에서 지속 형 계산 열의 이점은 무엇입니까? 파티 테이블의 기본 키는 이미 그룹 ID와 사용자 ID가 겹치지 않도록 보장하므로 외래 키는 PartyId에만 있으면됩니다. 작성된 모든 쿼리는 어쨌든 PartyTypeName의 테이블을 알아야합니다.
Arin Taylor

1
@ArinTaylor 지속 형 열은 사용자 유형의 당사자를 만들고 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있으며 둘 다 nullable 외래 키입니다. (다른 두 테이블의 각 열은 이에 따라 기본 키가됩니다.) CK_Ticket_GroupUser검사 제약 조건은 두 외래 키 열 중 하나만 참조를 포함하도록합니다 (다른 하나는 NULL이므로 둘 다 null을 허용해야 함).

( Ticket.ID이 특정 구현에는 기본 키 가 필요하지 않지만 이와 같은 테이블 에 기본 키 가있는 것은 확실히 해가되지 않습니다.)


1
이것은 또한 우리 소프트웨어에있는 것이므로 일반적인 데이터 액세스 프레임 워크를 만들려는 경우 피할 것입니다. 이 디자인은 앱 레이어의 복잡성을 증가시킵니다.
Frank.Germain

4
저는 SQL을 처음 접했기 때문에 이것이 잘못된 경우 정정 해 주지만이 디자인은 두 가지 소유자 유형의 티켓 만 필요하다고 매우 확신 할 때 사용하는 접근 방식 인 것 같습니다. 세 번째 티켓 소유자 유형이 도입 된 경우 테이블에 세 번째 Null 허용 외래 키 열을 추가해야합니다.
Shadoninja

@Shadoninja : 당신은 틀리지 않았습니다. 사실, 그것은 완전히 공정한 방법이라고 생각합니다. 나는 일반적으로 정당화되는 이런 종류의 솔루션에 대해 괜찮습니다. 그러나 옵션을 고려할 때 제 마음에 가장 먼저 떠오르지는 않을 것입니다. 정확하게 설명하신 이유 때문입니다.
Andriy M

2
@ Frank.Germain이 경우 두 개의 열을 기반으로 고유 외래 키 사용 RefID, 목표 테이블의 고정 식별자입니다. 무결성이 필요한 경우 트리거 또는 앱 계층에서 검사를 수행 할 수 있습니다. 이 경우 일반 검색이 가능합니다. SQL은 이와 같은 FK 정의를 허용해야 우리의 삶이 더 쉬워집니다. RefTypeRefType
djmj

2

또 다른 옵션은 Ticket소유 엔터티 유형 ( User또는 Group)을 지정하는 하나의 열 , 참조 된 User또는 GroupID가있는 두 번째 열이 있고 외래 키를 사용하지 않고 대신 트리거를 사용하여 참조 무결성을 적용하는 것입니다.

Nathan의 우수한 모델 (위)에 비해 두 가지 장점이 있습니다 .

  • 보다 즉각적인 명확성과 단순성.
  • 더 간단한 쿼리 작성.

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과 매우 유사하지만 (선호도에 따라) 다르게 보입니다.

각각의 새로운 소유자 유형에 대해 새 테이블을 가지지 않으려는 경우 각 잠재적 소유자에 대한 외래 키 열 대신 owner_type을 포함하는 것이 좋습니다.

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.