SQL一对一关系定义

时间:2011-06-07 03:39:07

标签: sql database-design

我正在设计一个数据库,但我不确定如何定义其中一个关系。情况如下:

  1. 已创建发票
  2. 如果产品没有库存则需要制造,因此会创建工作订单。
  3. 这种关系是一对一的。但是,有时会为其他目的创建工作订单,因此WorkOrder表也将以类似的一对一关系链接到其他表。此外,一些发票根本没有工作订单。这意味着我不能通过在两个表中使用相同的主键以正常方式定义这些关系。我没有这样做,而是创建了一个链接表,然后在两个字段上设置唯一索引来定义一对一的关系(见图)。

    DB Diagram http://markevans.org/temp/onetoone.gif

    这是最好的方法吗?

    干杯
    马克

    编辑:我刚刚意识到这个设计允许将单个工单与一张发票相关联,也可以通过2个链接表链接到我提到的其他表之一。我想没有解决方案是完美的。

5 个答案:

答案 0 :(得分:3)

好的,这个答案是特定于SQL Server的,但是应该可以适应其他RDBMS,只需要做一些工作。据我所知,我们有以下限制:

  • 发票可能与0或1个工作订单相关联
  • 工单必须与发票或ABC或DEF相关联

我按如下方式设计了WorkOrder表:

CREATE TABLE WorkOrder (
     WorkOrderID int IDENTITY(1,1) not null,
     /* Other Columns */
     InvoiceID int null,
     ABCID int null,
     DEFID int null,
     /* Etc for other possible links */
     constraint PK_WorkOrder PRIMARY KEY (WorkOrderID),
     constraint FK_WorkOrder_Invoices FOREIGN KEY (InvoiceID) references Invoice (InvoiceID),
     constraint FK_WorkOrder_ABC FOREIGN KEY (ABCID) references ABC (ABCID),
     /* Etc for other FKs */
     constraint CK_WorkOrders_SingleFK CHECK (
          CASE WHEN InvoiceID is null THEN 0 ELSE 1 END +
          CASE WHEN ABCID is null THEN 0 ELSE 1 END +
          CASE WHEN DEFID is null THEN 0 ELSE 1 END
          /* + other FK columns */
          = 1
     )
)

因此,基本上,无论定义了多少个PK,此表都仅限于FK到另一个表。如果有必要,计算列可以告诉您链接到的项目的“类型”,基于哪个FK列是非空的,或者类型和单个int列可以是实列,以及InvoiceID,ABCID等可以计算列。

最后要确保的是发票只有0或1个工作订单。如果您的RDMBS在唯一约束中忽略空值,则这就像将这样的约束应用于每个FK列一样简单。对于SQL Server,您需要使用筛选索引(> = 2008)或索引视图(< = 2005)。我只是展示过滤后的索引:

CREATE UNIQUE INDEX IX_WorkItems_UniqueInvoices on
    WorkItem (InvoiceID) where (InvoiceID is not null)

另一种处理保持WorkOrders直接的方法是在WorkOrder中包含一个WorkOrder类型列(例如'Invoice','ABC','DEF'),包括由check约束计算或列约束以包含匹配值链接表,并引入第二个外键:

CREATE TABLE WorkOrder (
     WorkOrderID int IDENTITY(1,1) not null,
     Type varchar(10) not null,
     constraint PK_WorkOrder PRIMARY KEY (WorkOrderID),
     constraint UQ_WorkOrder_TypeCheck UNIQUE (WorkOrderID,Type),
     constraint CK_WorkOrder_Types CHECK (Type in ('INVOICE','ABC','DEF'))
)
CREATE TABLE Invoice_WorkOrder (
     InvoiceID int not null,
     WorkOrderID int not null,
     Type varchar(10) not null default 'INVOICE',
     constraint PK_Invoice_WorkOrder PRIMARY KEY (InvoiceID),
     constraint UQ_Invoice_WorkOrder_OrderIDs UNIQUE (WorkOrderID),
     constraint FK_Invoice_WorkOrder_Invoice FOREIGN KEY (InvoiceID) references Invoice (InvoiceID),
     constraint FK_Invoice_WorkOrder_WorkOrder FOREIGN KEY (WorkOrderID) references WorkOrder (WorkOrderID),
     constraint FK_Invoice_WorkOrder_TypeCheck FOREIGN KEY (WorkOrderID,Type) references WorkOrder (WorkOrderID,Type),
     constraint CK_Invoice_WorkOrder_Type CHECK (Type = 'INVOICE')
)

此模型的唯一缺点是,虽然更接近原始提案,但您可以拥有一个实际上并未链接到任何其他项目的工单(尽管它声称是例如INVOICE)。

答案 1 :(得分:1)

您所拥有的只是一种完美normal构建表格的方式。

如果您认为您可能只想在WorkOrder表和可能有WorkOrders的其他表之间使用一个链接表,则可以使用如下链接表:

WorkOrders
OtherId (Could be InvoiceId, or an ID for SomethingElse that may have a WorkOrder)
OtherType (ENUM - something like 'Invoice', 'SomethingElse')
WorkOrderId

答案 2 :(得分:0)

所以问题是你可以拥有没有工单的发票和没有发票的工单,但是当有链接时需要链接这两个发票。我会说基于该描述你的数据库图表非常好。这将使您开启允许超过一对一的关系。通过这种方式,您可以考虑为一张发票订购两份工单。您可能还有一个处理两张发票的工单。这为您提供了许多您现在可能不需要的可能性,但将来可能会这样。

我会推荐你​​当前的设计。将来,您可能希望添加有关发票和工作单之间链接的更多信息。此中间表将允许您添加此信息。

为了公平对待硬币的另一面,你需要考虑速度/表数/等。这将导致。例如,您现在已经创建了第三个表,在此示例中将表计数增加了50%。查看数据库的其余部分。如果你到处都这样做,你可能拥有最规范化的数据库,但由于所有必要的连接,它可能不是最高性能的数据库。基本上,这不是一个“一刀切”的解决方案。相反,它是一种设计选择。就个人而言,我讨厌可以入侵的外键领域。我发现他们没有给我我通常想要的数据库设计的粒度。

答案 3 :(得分:0)

您的架构对应于2个表之间的多对多链接。事实上,您可以在此处打开多个发票的工作单和一张发票的多个工单的可能性。该模型提供的可能性远远高于您设定的规则。

您可以使用更简单的架构,它将反映工作订单和发票之间的(0,1)关系,以及发票和工作订单之间的(0,1)关系:

  • 工作订单可以独立于 发票,或链接到一个特定的 invoice:与发票表
  • 有(0,1)关系
  • 发票上没有工作单或一个工单:它与工作订单表有(0,1)的关系

这种关系可以通过以下模型和规则进行翻译

Invoice
    id_Invoice, Primary Key

WorkOrder
    id_WorkOrder, Primary Key
    id_Invoice, Foreign Key, Nulls accepted, unique value

使用这样的结构,可以很容易地将新的“依赖”添加到工单表中。例如,如果您想要通过重新进货订单(您希望库存中的某些商品的数量最少)启动工作订单,则可以将相应的字段添加到WorkOrder表中:

    id_RestockingOrder, ForeignKey, Nulls accepted, unique value

然后,您就可以“看到”WorkOrder的来源:发票,补货订单等。

似乎它符合您的需求。

编辑:

如@mark所述,SQL Server不允许多个空值,与ANSI规范相矛盾(更多细节请查看here),因为我们不想等待SQL Server 2011拥有这个实施规则后,有一个解决方法here,您可以在其中构建一个不包含空值的视图,并在此视图上设置唯一索引。我必须承认我不喜欢这个解决方案...

仍然有可能在代码中实现'unique not null'规则。它仍然比实现多对多模型(使用Invoice_WorkOrder表)更简单,您建议并管理您需要实现的所有其他unicity规则。

答案 4 :(得分:-1)

实际上不需要链接表,只需将它们直接链接并在工作订单的引用字段中允许NULL。因为工作订单可以链接到多个表,我会做的是在每个工单上添加一个引用ID到每个可以从中链接的表。所以你会:

Invoice
PK - ID
FK - WorkOrderID

SomeOtherTable
PK - ID
FK - WorkOrderID

WorkOrder
PK - ID
FK - InvoiceID (allow NULL)
FK - SomeOtherTableID (allow NULL)

要确保WorkOrder只链接到一个项目,您必须使用代码来验证行(或者可能是我现在无法想出的存储过程)。

编辑:PS,如果你想使用链接表,给它一个通用名称,并添加所有链接表,使用我刚才描述的允许NULL的相同类型的构造。在我看来,添加额外的表使得模式比需要的更大,但是如果工作单包含大量的大文本字段,它可以稍微提高性能并减少数据库大小,所有索引都会飞来飞去。除了最大的应用程序之外,我会认为它过度规范化,但这是一种风格问题。