Como você está usando campos anuláveis para as chaves estrangeiras, é possível construir um sistema que funcione corretamente da maneira que você o imagina. Para inserir linhas na tabela Contas, você precisa ter uma linha presente na tabela Contatos, a menos que permita inserções nas Contas com um PrimaryContactID nulo. Para criar uma linha de contato sem já ter uma linha de Conta presente, você deve permitir que a coluna Código da Conta na tabela Contatos seja anulável. Isso permite que as contas não tenham contatos e permite que os contatos não tenham conta. Talvez isso seja desejável, talvez não.
Dito isto, minha preferência pessoal seria ter a seguinte configuração:
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;
Isso fornece a capacidade de:
- Delineie claramente os relacionamentos entre contatos e contas por meio de uma tabela de referência cruzada da maneira como Pieter recomenda em sua resposta
- Mantenha a integridade referencial de maneira não circular.
- Forneça uma lista altamente sustentável de contatos principais por meio do
IX_AccountsContactsXRef_Primary
índice. Este índice contém um filtro, portanto, ele funcionará apenas em plataformas que os suportem. Como esse índice é especificado com a UNIQUE
opção, só pode haver um único contato principal para cada conta.
Por exemplo, se você deseja exibir uma lista de todos os contatos, com uma coluna indicando o status "primário", mostrando os contatos principais no topo da lista de cada conta, você pode:
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;
O índice filtrado impede a inserção de mais de um único contato principal por conta, ao mesmo tempo em que fornece um método rápido de retornar uma lista de contatos principais. Pode-se imaginar facilmente outra coluna, IsActive
com um índice filtrado não exclusivo para manter um histórico de contatos por conta, mesmo depois que esse contato não estiver mais associado à conta:
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;