web-dev-qa-db-fra.com

Clé étrangère à plusieurs tables

J'ai 3 tables pertinentes dans ma base de données.

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
)

Les utilisateurs appartiennent à plusieurs groupes. Cela se fait via une relation plusieurs à plusieurs, mais non pertinente dans ce cas. Un ticket peut appartenir à un groupe ou à un utilisateur via le champ dbo.Ticket.Owner.

Quelle serait la façon MOST CORRECT de décrire cette relation entre un ticket et éventuellement un utilisateur ou un groupe?

Je pense que je devrais ajouter un drapeau dans la table des tickets qui indique à quel type appartient le type.

109
Darthg8r

Vous avez quelques options, toutes variant dans "l'exactitude" et la facilité d'utilisation. Comme toujours, le bon design dépend de vos besoins.

  • Vous pouvez simplement créer deux colonnes dans Ticket, OwnedByUserId et OwnedByGroupId, et avoir des clés étrangères nullables pour chaque table.

  • Vous pouvez créer des tables de référence M: M activant à la fois les relations ticket: utilisateur et ticket: groupe. Peut-être qu’à l’avenir vous voudrez permettre à un seul ticket d’être la propriété de plusieurs utilisateurs ou groupes? Cette conception n'impose pas qu'un ticket doit soit la propriété d'une seule entité.

  • Vous pouvez créer un groupe par défaut pour chaque utilisateur et faire en sorte que les tickets appartiennent simplement à un véritable groupe ou au groupe par défaut d'un utilisateur.

  • Ou (à mon choix) modélisez une entité qui sert de base aux utilisateurs et aux groupes et dont les tickets sont la propriété de cette entité.

Voici un exemple approximatif utilisant votre schéma posté:

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
)
135
Nathan Skerl

La première option de la liste de @ Nathan Skerl est celle qui a été implémentée dans un projet avec lequel j'ai déjà travaillé, dans laquelle une relation similaire a été établie entre trois tables. (L'un d'eux en a référé deux autres, un à la fois.)

Ainsi, la table de référence avait deux colonnes de clé étrangère et une contrainte pour garantir qu’une seule table (ni les deux, ni les deux) ne soit référencée par une seule ligne.

Voici à quoi cela pourrait ressembler lorsqu'il est appliqué à vos tables:

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

Comme vous pouvez le constater, la table Ticket comporte deux colonnes, OwnerGroup et OwnerUser, qui sont toutes deux des clés étrangères nullables. (Les colonnes respectives dans les deux autres tables sont transformées en clés primaires en conséquence.) Le CK_Ticket_GroupUser _ contrainte de vérification garantit qu'une seule des deux colonnes de clé étrangère contient une référence (l'autre étant NULL, les deux doivent donc être nulles).

(La clé primaire sur Ticket.ID n'est pas nécessaire pour cette implémentation particulière, mais il ne serait certainement pas préjudiciable d'en avoir un dans un tableau comme celui-ci.)

27
Andriy M

Une autre option consiste à avoir, dans Ticket, une colonne spécifiant le type d’entité propriétaire (User ou Group), une deuxième colonne référencée User ou Group id et NE PAS utiliser de clés étrangères, mais plutôt s'appuyer sur un déclencheur pour appliquer l'intégrité référentielle.

Deux avantages que je vois ici par rapport à Nathan excellent modèle (ci-dessus):

  • Clarté et simplicité plus immédiates.
  • Des requêtes plus simples à écrire.
0
Jan Żankowski