Puisque vous utilisez des champs nullables pour les clés étrangères, vous pouvez en fait construire un système qui fonctionne correctement comme vous l'imaginez. Afin d'insérer des lignes dans la table des comptes, vous devez avoir une ligne présente dans la table des contacts, sauf si vous autorisez les insertions dans les comptes avec un PrimaryContactID nul. Afin de créer une ligne de contact sans avoir déjà une ligne de compte présente, vous devez autoriser la colonne AccountID dans la table Contacts pour être nullable. Cela permet aux comptes de ne pas avoir de contacts et aux contacts de ne pas avoir de compte. C'est peut-être souhaitable, peut-être pas.
Cela dit, ma préférence personnelle serait d'avoir la configuration suivante:
CREATE TABLE dbo.Accounts
(
AccountID INT NOT NULL
CONSTRAINT PK_Accounts
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, AccountName VARCHAR(255)
);
CREATE TABLE dbo.Contacts
(
ContactID INT NOT NULL
CONSTRAINT PK_Contacts
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, ContactName VARCHAR(255)
);
CREATE TABLE dbo.AccountsContactsXRef
(
AccountsContactsXRefID INT NOT NULL
CONSTRAINT PK_AccountsContactsXRef
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, AccountID INT NOT NULL
CONSTRAINT FK_AccountsContactsXRef_AccountID
FOREIGN KEY REFERENCES dbo.Accounts(AccountID)
, ContactID INT NOT NULL
CONSTRAINT FK_AccountsContactsXRef_ContactID
FOREIGN KEY REFERENCES dbo.Contacts(ContactID)
, IsPrimary BIT NOT NULL
CONSTRAINT DF_AccountsContactsXRef
DEFAULT ((0))
, CONSTRAINT UQ_AccountsContactsXRef_AccountIDContactID
UNIQUE (AccountID, ContactID)
);
CREATE UNIQUE INDEX IX_AccountsContactsXRef_Primary
ON dbo.AccountsContactsXRef(AccountID, IsPrimary)
WHERE IsPrimary = 1;
Cela permet de:
- Délimitez clairement les relations entre les contacts et les comptes grâce à une table de correspondance, comme le recommande Pieter dans sa réponse
- Maintenir l'intégrité référentielle de manière saine et non circulaire.
- Fournissez une liste hautement maintenable de contacts principaux via l'
IX_AccountsContactsXRef_Primary
index. Cet index contient un filtre, il ne fonctionnera donc que sur les plates-formes qui les prennent en charge. Étant donné que cet index est spécifié avec l' UNIQUE
option, il ne peut y avoir qu'un seul contact principal pour chaque compte.
Par exemple, si vous souhaitez afficher une liste de tous les contacts, avec une colonne indiquant le statut "principal", montrant les contacts principaux en haut de la liste pour chaque compte, vous pouvez faire:
SELECT A.AccountName
, C.ContactName
, XR.IsPrimary
FROM dbo.Accounts A
INNER JOIN dbo.AccountsContactsXRef XR ON A.AccountID = XR.AccountID
INNER JOIN dbo.Contacts C ON XR.ContactID = C.ContactID
ORDER BY A.AccountName
, XR.IsPrimary DESC
, C.ContactName;
L'index filtré empêche l'insertion de plus d'un seul contact principal par compte, tout en fournissant simultanément une méthode rapide de renvoi d'une liste de contacts principaux. On pourrait facilement imaginer une autre colonne, IsActive
avec un index filtré non unique pour conserver un historique des contacts par compte, même après que ce contact ne soit plus associé au compte:
ALTER TABLE dbo.AccountsContactsXRef
ADD IsActive BIT NOT NULL
CONSTRAINT DF_AccountsContactsXRef_IsActive
DEFAULT ((1));
CREATE INDEX IX_AccountsContactsXRef_IsActive
ON dbo.AccountsContactsXRef(IsActive)
WHERE IsActive = 1;