Dato che stai usando campi nullable per le chiavi esterne, puoi infatti costruire un sistema che funzioni correttamente come lo vedi. Per inserire righe nella tabella Account è necessario disporre di una riga presente nella tabella Contatti a meno che non si consentano inserimenti in Account con un PrimaryContactID nullo. Per creare una riga di contatto senza disporre già di una riga Account, è necessario rendere nulla la colonna AccountID nella tabella Contatti. Ciò consente agli account di non avere contatti e ai contatti di non avere account. Forse questo è desiderabile, forse no.
Detto questo, la mia preferenza personale sarebbe quella di avere la seguente configurazione:
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;
Ciò consente di:
- Delinea chiaramente le relazioni tra contatti e account attraverso una tabella di riferimenti incrociati come Pieter raccomanda nella sua risposta
- Mantenere l'integrità referenziale in modo solido e non circolare.
- Fornire un elenco altamente gestibile di contatti primari tramite l'
IX_AccountsContactsXRef_Primary
indice. Questo indice contiene un filtro, quindi funzionerà solo su piattaforme che li supportano. Poiché questo indice è specificato con l' UNIQUE
opzione, può esserci un solo contatto primario per ogni account.
Ad esempio, se si desidera visualizzare un elenco di tutti i contatti, con una colonna che indica lo stato "principale", che mostra i contatti primari nella parte superiore dell'elenco per ciascun account, è possibile:
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'indice filtrato impedisce l'inserimento di più di un singolo contatto principale per account, fornendo contemporaneamente un metodo rapido per restituire un elenco di contatti primari. Si potrebbe facilmente immaginare un'altra colonna, IsActive
con un indice filtrato non univoco per mantenere una cronologia dei contatti per account, anche dopo che quel contatto non è più associato all'account:
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;