【问题标题】:How do I check constraints between two tables when inserting into a third table that references the other two tables?插入引用其他两个表的第三个表时,如何检查两个表之间的约束?
【发布时间】:2010-10-04 16:10:19
【问题描述】:

考虑这个示例架构:

Customer ( int CustomerId pk, .... )

Employee ( int EmployeeId pk,
           int CustomerId references Customer.CustomerId, .... )

WorkItem ( int WorkItemId pk,
           int CustomerId references Customer.CustomerId,
           null int EmployeeId references Employee.EmployeeId, .... )

基本上三个表:

  • 具有主键和一些附加列的客户表
  • 具有主键的员工表,外键约束引用客户表的主键,代表客户的员工。
  • 工作项表,其中存储为客户完成的工作,以及有关为其执行工作的特定员工的信息。

我的问题是。在添加新工作项时,我如何在数据库级别测试员工是否确实与客户相关联。

例如,如果 Scott(员工)在 Microsoft(客户)工作,而 Jeff(员工)在 StackOverflow(客户)工作,我如何防止某人将工作项添加到数据库中,其中 customer = Microsoft 和员工= Jeff,这没有意义?

我可以用检查约束或外键来做吗,还是需要一个触发器来手动测试它?

应该提到我使用的是 SQL Server 2008。

更新:我应该补充一点,WorkItem.EmployeeId 可以为空。

谢谢,埃吉尔。

【问题讨论】:

    标签: sql sql-server database-design foreign-keys check-constraints


    【解决方案1】:

    复合列(CustomerId、EmployeeId)上的外键不起作用吗?

    ALTER TABLE WorkItem
    ADD CONSTRAINT FK_Customer_Employee FOREIGN KEY (CustomerId, EmployeeId)
        REFERENCES Employee (CustomerId, EmployeeId);
    

    【讨论】:

    • 抱歉,忘了说 WorkItem.EmployeeId 可以为空。以上方法还能用吗?
    • 嗯,不确定;我认为一些 RDBMS 可以处理,而另一些则不能,我不知道 SQL Server 是否可以。如果没有,您也许可以为每个客户定义一个“无人”员工。否则将需要更复杂的 CHECK 条件。
    • 好的,谢谢。因此,当我尝试执行上面的 SQL 时,我收到以下错误消息:引用表 'Employee' 中没有与外键 'FK_Customer_Employee' 中的引用列列表匹配的主键或候选键。我的 Employee 表中缺少什么?
    • 您需要在 Employee(EmployeeId, CustomerId) 上创建一个唯一索引。实际上,为什么那不是您的主键?
    • @derobert:好问题。正如我在其他地方提到的那样,这不是我有意识的决定,我的 SQL 知识是基本的。感谢您的意见!
    【解决方案2】:

    您可以通过创建一个“WITH SCHEMABINDING”视图来实现这一点,该视图跨越这些表并强制各个表的集体约束。

    【讨论】:

    • +1:非常有创意。我以前从未听说过这种解决方案。 // 这是 Stackoverflow 如此酷的一个完美例子。我永远不会自己找到这个。
    【解决方案3】:

    为什么你希望employeeId 为null int WorkItem?也许您应该添加另一个表以避免这种特殊的奇怪情况。据我所知,最简单的做法是在 workItem 中为employeeid 添加唯一约束,如果您想要的话,甚至可以在customerId 上添加唯一约束。

    添加跨多个表的约束的更通用方法是定义一个应始终为空的视图,并添加它为空的约束。

    【讨论】:

    • 有时没有员工与工作项相关联,这就是为什么我想让 EmployeeId 为空。
    【解决方案4】:

    你想在这里建模什么?

    1. 您是承包机构或类似机构,并且您有一堆(在一段时间内)分配给客户的承包商。

    2. 您实际上是在存储有关其他公司员工的信息(例如,您可能正在提供外包工资单服务)。

    在情况 (1) 中,您的 Employee 表似乎有问题。特别是当 Scott 与 MS 的合同到期并且他与其他人签约时,您无法保留历史数据,因为您需要更改 CustomerId。这也使所有WorkItems 无效。相反,您应该有第四个表,例如 CustomerEmployee 来存储它。然后WorkItem 应该引用该表。

    在情况 (2) 中,您在 Employee 上的主键实际上应该是 CustomerIdEmployeeId。两个客户可以有相同的员工 ID 号。然后 Kieron 的外键就可以工作了。

    【讨论】:

      【解决方案5】:

      我最近遇到了类似的情况,考虑一下架构: 表公司(id_cia PK)表product_group(id_cia FK到公司,id_group PK)表产品(id_group FK到product_group,id_product PK,id_used_by_the_client null)

      规则:数据库必须为公司的每个产品只允许一个id_used_by_the_client,但该字段可以为空。示例:

      插入公司 (1) = 允许

      插入公司 (2) = 允许

      插入 product_group (1, 1) = 允许

      插入product_group (1,2) = 允许

      插入product_group (2,3) = 允许

      插入产品值 (1, 1, null) = 允许

      插入产品值 (1, 2, null) = 允许

      插入产品值 (1, 3, 1) = 允许

      插入产品值 (1, 4, 1) = 不允许,在属于公司 1 的组 1 中已经存在 id_used_by_the_client = 1。

      插入产品值 (2, 4, 1) = 不允许,在属于公司 1 的组 2 中已经存在 id_used_by_the_client = 1。

      插入产品值 (3, 4, 1) = 允许,在属于公司 2 的组 3 中没有 id_used_by_the_client = 1。

      我决定使用触发器来控制这种完整性。

      【讨论】:

        【解决方案6】:

        要么:

        • 将 EmployeeID 列设为 Employee 的主键(也可能是自动 ID),并将 EmployeeID 作为外键存储在 WorkItem 记录中,而不是将 Employee 和 Customer ID 存储在 WorkItem 中。您可以通过 Employee 表加入 Customer 表来检索 WorkItem 的 Customer 详细信息。

        或者:

        • 使 WorkItem 的 EmployeeID 和 CustomerID 列成为 Employee 的复合外键。

        我个人更喜欢第一种方法。

        【讨论】:

        • 您的第一种方法并不真正适合,因为并不总是有员工与工作项相关联。我不确定方法二是否也适用于这种情况。
        • 啊,所以 WorkItem 总是有一个 Customer,但可能有也可能没有 Employee?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-23
        • 1970-01-01
        • 2011-09-13
        • 2016-01-24
        • 2021-09-22
        • 1970-01-01
        相关资源
        最近更新 更多