零或一到零或一


9

如何以最自然的方式在Sql Server中对零或一到零或一的关系建模?

有一个“危险”表,列出了站点上的危险。有一个“任务”表,用于需要在站点上完成的工作。有些任务是修复危险,没有任务可以应对多种危险。有些危害有修复它们的任务。没有任何危险可以使两个任务相关联。

以下是我能想到的最好的方法:

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

你会做不同的吗?我对此设置不满意的原因是,需要使用应用程序逻辑来确保任务和危害指向彼此,而不是其他任务和危害,并且没有任务/危害指向相同的危害/任务另一个任务/危险指向。

有没有更好的办法?

在此处输入图片说明


有什么原因不能在危险表上的TaskID上创建唯一索引,而在任务表上不能创建HazardID唯一索引?这样一来,您就只能在表中包含其中之一,这是我认为您想要实现的目标。
mskinner

@mskinner,但它们不是唯一的,很多都可以null
Andrew Savinykh

啊,知道了 在那种情况下,Ben-Gan先生在sqlmag.com/sql-server-2008/unique-constraint-multiple-nulls上写了大量关于如何创建约束以允许多个null的 文章。我认为这对您有用。让我知道是否。
mskinner

附带说明一下,使用过滤索引存在许多问题,因此如果您不熟悉,则可能值得一读。这是一个很好的博客。 blogs.msdn.com/b/sqlprogrammability/archive/2009/06/29/… ,但是对于这种特定情况,假设其他问题不会给您带来太多麻烦,它可能对您来说效果很好。
mskinner

FWIW唯一的经过过滤的索引只能将唯一性应用于非空行,例如CREATE UNIQUE INDEX x ON dbo.Hazards(TaskID) WHERE TaskID IS NOT NULL;
Aaron Bertrand

Answers:


9

您可以通过从当前设置中删除一个外键来采用非对称模式的想法,或者,为了保持对称,可以删除两个外键并引入对每个引用具有唯一约束的联结表

因此,它将是这样的:

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

(HazardId, TaskId)如果需要从另一个表引用这些组合,则可以另外声明是主键。但是,为了使两对保持唯一,主键是不必要的,每个ID都是唯一的就足够了。


1
+1我认为这是实现这种关系的最自然的方法。但是通常只有在最小时才选择一个元组作为主元组。元组(HazardId, TaskId)具有元组(HazardId)(TaskId)并且都唯一地标识该表的一行。应该选择其中之一作为主键。
miracle173

2

总结一下:

  • 危险有一个或零个任务
  • 任务具有一种或零种危害

如果“任务”和“危害”表用于其他用途(即,任务和/或危害具有其他数据关联,并且您显示给我们的模型被简化为仅显示相关字段),那么您的解决方案是正确的。

否则,如果仅将任务和危险相互关联,则您不需要两个表。您可以使用以下字段为其关系创建一个表:

ID            int, PK
TaskID        int, (filtered) unique index  
TaskDetails   varchar
HazardID      int, (filtered) unique index
HazardDetails varchar

1
我可以自由地澄清一下,在每种情况下都应将其作为过滤索引(尽管可以从问题的描述中猜测出来,但看起来可能并不十分明显)。如果您认为不必要或想要以其他方式传达想法,请随时进行进一步的编辑。
Andriy M

我必须说我对这个想法仍然不是很满意。问题是我必须手动生成TaskID和HazardID。如果是2012年,则可以使用序列,但即使那样对我来说似乎也不对。我猜这是因为任务和危险这两个事物是完全不同的实体,因此很难将它们存储在单个表中。
Andriy M 2015年

如果选择此设计,为什么我们需要TaskIDHazardID?你可以有2个列IsTaskIsHazard和约束,不是他们两个都是假的。然后,该HazardCRAETE VIEW Hazard SELECT HazardID = ID, HazardDetails, ... FROM ThisTable WHERE IsHazard = 1;分别只是一个视图:和任务。
ypercubeᵀᴹ

@ypercube您将要在表中存储无形的东西,然后使用二进制字段来判断它是任务还是危险。这是丑陋的数据库建模。
dr_

@AndriyM我了解,并且我同意,在大多数情况下,我们不应该在同一张表中存储两种不同的实体。但是,请阅读我的警告:如果“任务”和“危险”之间是一对一的关系,并且仅为此存在,那么使用单个表是可以接受的。
dr_

1

似乎尚未提及的另一种方法是让危害和任务使用相同的ID空间。如果危害具有任务,它将具有相同的ID。如果任务是危险的,它将具有相同的ID。

您将使用序列而不是标识列来填充这些ID。

对此类数据模型的查询将使用(完整)外部联接来检索其结果。

此方法与@AndriyM的答案非常相似,除了他的答案允许ID不同以及用于存储此关系的表外。

我不确定您是否要在两张表的情况下使用此方法,但是当涉及的表数增加时,它会很好地工作。


感谢您的贡献,我也考虑过这种方法。最后我决定反对它,因为在sql2008中实现序列很麻烦,而且比我愿意忍受的麻烦更多。
Andrew Savinykh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.