zero ou um a zero ou um

9

Como modelar o relacionamento zero ou um para zero ou um no Sql Server da maneira mais natural?

Existe uma tabela 'Perigos' que lista os perigos em um site. Há uma tabela 'Tarefa' para o trabalho que precisa ser realizado em um site. Algumas tarefas são para corrigir um risco, nenhuma tarefa pode lidar com vários riscos. Alguns perigos têm uma tarefa para corrigi-los. Nenhum risco pode ter duas tarefas associadas a eles.

O abaixo é o melhor que eu poderia pensar:

CREATE TABLE [dbo].[Hazard](
  [HazardId] [int] IDENTITY(1,1) NOT NULL,
  [TaskId] [int] NULL,
  [Details] [varchar](max) NULL,
 CONSTRAINT [PK_Hazard] PRIMARY KEY CLUSTERED 
(
  [HazardId] ASC
))

GO

ALTER TABLE [dbo].[Hazard]  WITH CHECK ADD  CONSTRAINT [FK_Hazard_Task] FOREIGN KEY([TaskId])
REFERENCES [dbo].[Task] ([TaskId])
GO


CREATE TABLE [dbo].[Task](
  [TaskId] [int] IDENTITY(1,1) NOT NULL,
  [HazardId] [int] NULL,
  [Details] [varchar](max) NULL,
 CONSTRAINT [PK_Task] PRIMARY KEY CLUSTERED 
(
  [TaskId] ASC
))

GO

ALTER TABLE [dbo].[Task]  WITH CHECK ADD  CONSTRAINT [FK_Task_Hazard] FOREIGN KEY([HazardId])
REFERENCES [dbo].[Hazard] ([HazardId])
GO

Você faria isso de maneira diferente? O motivo pelo qual não estou satisfeito com essa configuração é que é preciso haver uma lógica de aplicativo para garantir que as tarefas e os perigos apontem um para o outro e não para outras tarefas e perigos e que nenhuma tarefa / perigo aponte para o mesmo perigo / tarefa outra tarefa / perigo aponta para.

Existe uma maneira melhor?

insira a descrição da imagem aqui

Andrew Savinykh
fonte
Existe algum motivo para você não conseguir criar um índice exclusivo no TaskID na tabela Hazard e um índice exclusivo de HazardID na tabela Task? Isso faria com que você só pudesse ter um deles na tabela, que é o que você está tentando realizar.
mskinner
@mskinner mas eles não são únicos, muitos deles podem ser null.
Andrew Savinykh
ah, entendi. Nesse caso, o Sr. Ben-Gan escreveu um ótimo artigo sobre como criar essa restrição para permitir vários nulos aqui sqlmag.com/sql-server-2008/unique-constraint-multiple-nulls . Eu acho que isso funcionará para você. Deixe-me saber se não.
mskinner
Como uma observação lateral disso, existem vários problemas ao usar índices filtrados; portanto, provavelmente valeria a pena ler sobre eles se você não estiver familiarizado. Aqui está um bom blog sobre isso. blogs.msdn.com/b/sqlprogrammability/archive/2009/06/29/… , mas para esse cenário específico, pode funcionar bem para você, supondo que os outros problemas não causem muito sofrimento a você.
mskinner
FWIW, um índice filtrado exclusivo pode aplicar exclusividade apenas às linhas não nulas, por exemploCREATE UNIQUE INDEX x ON dbo.Hazards(TaskID) WHERE TaskID IS NOT NULL;
Aaron Bertrand

Respostas:

9

Você pode seguir sua própria idéia de um esquema assimétrico removendo uma das chaves estrangeiras da configuração atual ou, para manter as coisas simétricas, você pode remover as duas chaves estrangeiras e introduzir uma tabela de junção com uma restrição exclusiva em cada referência. .

Então, seria assim:

CREATE TABLE dbo.Hazard
(
  HazardId int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Hazard PRIMARY KEY CLUSTERED,
  Details varchar(max) NULL
);

CREATE TABLE dbo.Task
(
  TaskId int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Task PRIMARY KEY CLUSTERED,
  Details varchar(max) NULL,
);

CREATE TABLE dbo.HazardTask
(
  HazardId int NOT NULL
    CONSTRAINT FK_HazardTask_Hazard FOREIGN KEY REFERENCES dbo.Hazard (HazardId)
    CONSTRAINT UQ_HazardTask_Hazard UNIQUE,
  TaskId int NOT NULL
    CONSTRAINT FK_HazardTask_Task FOREIGN KEY REFERENCES dbo.Task (TaskId)
    CONSTRAINT UQ_HazardTask_Task UNIQUE
);

Além disso, você pode declarar (HazardId, TaskId)ser a chave primária se precisar fazer referência a essas combinações de outra tabela. Para manter os pares exclusivos, no entanto, a chave primária é desnecessária, basta que cada ID seja único.

Andriy M
fonte
11
+1 Acho que essa é a maneira mais natural de implementar essa relação. mas geralmente só se seleciona uma tupla como primária se for mínima. A tupla (HazardId, TaskId)possui as tuplas (HazardId)e (TaskId)ambas identificam uma linha desta tabela exclusivamente. Um deles deve ser selecionado como chave primária.
precisa saber é o seguinte
2

Resumindo:

  • Os perigos têm uma ou zero tarefas
  • As tarefas têm um ou zero riscos

Se as tabelas Tarefa e Perigo forem usadas para outra coisa (por exemplo, tarefas e / ou perigos têm outros dados associados, e o modelo que você nos mostrou é simplificado para mostrar apenas os campos relevantes), eu diria que sua solução está correta.

Caso contrário, se existir apenas tarefas e riscos para serem acoplados, você não precisará de duas tabelas; você pode criar uma única tabela para o relacionamento deles, com os seguintes campos:

ID            int, PK
TaskID        int, (filtered) unique index  
TaskDetails   varchar
HazardID      int, (filtered) unique index
HazardDetails varchar
dr_
fonte
11
Tomei a liberdade de esclarecer que deveria ser um índice filtrado em cada caso (embora possa ser calculado a partir da descrição do problema, pode não parecer totalmente óbvio). Sinta-se à vontade para editar mais se achar desnecessário ou desejar transmitir a ideia de uma maneira diferente.
Andriy M
Devo dizer que ainda não estou muito feliz com essa ideia. O problema é que eu teria que gerar TaskID e HazardID manualmente. Se fosse 2012, seria possível usar sequências para isso, mas mesmo assim não me pareceria correto. Eu acho que é porque as duas coisas, tarefas e perigos, são entidades completamente diferentes e, portanto, é difícil se reconciliar com a idéia de armazená-las em uma única tabela.
Andriy M
Se esse design for escolhido, por que precisamos do TaskIDe HazardID? Você pode ter 2 colunas BIT IsTask, IsHazarde uma restrição de que ambas não sejam falsas. Então a Hazardtabela é simplesmente uma visualização: CRAETE VIEW Hazard SELECT HazardID = ID, HazardDetails, ... FROM ThisTable WHERE IsHazard = 1;e Tarefa, respectivamente.
precisa saber é o seguinte
@ypercube Você armazenaria algo sem forma em uma tabela e, em seguida, usaria um campo binário para saber se é uma tarefa ou um risco. É uma feia modelagem de banco de dados.
Dr_
@AndriyM Entendo e concordo que, na maioria dos casos , não devemos armazenar dois tipos de entidades diferentes na mesma tabela. No entanto, leia minha ressalva: se as tarefas e os riscos estão em um relacionamento 1 para 1 e existem apenas para isso , é aceitável usar uma única tabela.
Dr_
1

Outra abordagem que parece não ter sido mencionada ainda é fazer com que Riscos e tarefas usem o mesmo espaço de identificação. Se um perigo tiver uma tarefa, ele terá o mesmo ID. Se uma tarefa é para um perigo, ela terá o mesmo ID.

Você usaria uma sequência em vez de colunas de identidade para preencher esses IDs.

As consultas nesse tipo de modelo de dados usariam junções externas (completas) para recuperar seus resultados.

Essa abordagem é muito semelhante à resposta de @ AndriyM, exceto que sua resposta permite que os IDs sejam diferentes e uma tabela para armazenar esse relacionamento.

Não tenho certeza se você deseja usar essa abordagem para um cenário de duas tabelas, mas funciona bem quando o número de tabelas envolvidas aumenta.

Colin 't Hart
fonte
Obrigado por contribuir, também considerei essa abordagem. Eu decidi contra isso no final, porque a sequência é difícil de implementar no sql2008 e tem mais problemas do que eu gostaria de suportar.
Andrew Savinykh