在存在代理键的情况下,如何为域约束定义复合外键?

时间:2010-03-31 14:20:47

标签: ruby-on-rails postgresql foreign-keys composite-key surrogate-key

我正在用Rails编写一个新的应用程序,所以我在每个表上都有一个id列。使用外键强制执行域约束的最佳做法是什么?我将概述我的想法和挫折感。

这就是我想象的“The Rails Way”。这就是我的开始。

Companies:
    id: integer, serial
    company_code: char, unique, not null

Invoices:
    id: integer, serial
    company_id: integer, not null

Products:
    id: integer, serial
    sku: char, unique, not null
    company_id: integer, not null

LineItems:
    id: integer, serial
    invoice_id: integer, not null, references Invoices (id)
    product_id: integer, not null, references Products (id)

问题在于,一家公司的产品可能出现在另一家公司的发票上。我向LineItems添加了一个(company_id:integer,not null),就像我只使用自然键和连续符一样,然后添加了一个复合外键。

LineItems (product_id, company_id) references Products (id, company_id)
LineItems (invoice_id, company_id) references Invoices (id, company_id)

这恰当地将LineItems限制在一家公司,但它似乎过度设计并且错误。 LineItems中的company_id是无关紧要的,因为代理外键在外表中已经是唯一的。 Postgres要求我为引用的属性添加唯一索引,因此我在产品和发票中的(id,company_id)上创建了一个唯一索引,即使id只是唯一的。

下表中包含自然键和序列发票编号不会增加这种复杂性,因为引用的列已经是自然键,因此它们已经具有唯一索引。

LineItems:
    company_code: char, not null
    sku: char, not null
    invoice_id: integer, not null

我可以忽略LineItems表中的代理键,但这似乎也是错误的。为什么在数据库已经存在的情况下使数据库加入char?此外,完全按照上述方式执行此操作需要我将company_code(一种自然外键)添加到产品和发票中。

妥协......

LineItems:
    company_id: integer, not null
    sku: integer, not null
    invoice_id: integer, not null

在其他表中不需要自然外键,但是当有一个整数可用时,它仍然会加入char。

是否有一种干净的方法来强制使用像上帝预期的外键来限制域约束,但是在代理的存在下,却没有将模式和索引变成一个复杂的混乱?

1 个答案:

答案 0 :(得分:1)

你提到id在上面已经是唯一的了,但是你必须使id,company_id独特,因为你在FK引用中使用它,需要确保它只引用1个唯一的项而不是任何其他项。

我知道您想要“保护”数据,但我会说这是过度设计并同意您的意见。任何使用此数据且无法正常工作的系统(为公司提取正确的产品)都会立即引起注意。在编程时会增加处理更多数据的负担,并可能使事情更加混乱。

我喜欢保护数据的努力,但随着您获得更多数据,成本可能会增加,并且肯定会增加更复杂数据模型的负担。

我认为数据在应用程序中通过“自然”使用得到了足够的保护。当您开始创建新发票并且您正在寻找要在发票上打印的产品时,您将使用相同的公司ID来搜索用于创建发票的产品。如果你以某种方式在那里得到错误的ID(错误的代码,直接在DB中的用户)那么坏的结果将立即被注意到。

我真的不明白你帖子的第二部分。您似乎正试图沿着复杂的路径走,而不是创建一个简单的规范化架构。

为什么你这么担心非感知键?您是否有用户以非标准方式访问此数据?