1

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

这就是我想象的“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(自然外键)添加到 Products 和 Invoices。

妥协...

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

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

有没有一种干净的方法可以像上帝预期的那样使用外键强制域约束,但是在代理存在的情况下,而不会将模式和索引变成复杂的混乱?

4

1 回答 1

1

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

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

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

我认为通过应用程序中的“自然”使用,数据得到了足够的保护。当您创建新发票并寻找要放在发票上的产品时,您将使用与创建发票相同的公司 ID 来搜索产品。如果您以某种方式在其中获得了错误的 ID(错误的代码,直接在 DB 中的用户),那么糟糕的结果将立即显而易见。

我真的不明白你的帖子的第二部分。似乎您正在尝试走一条复杂的道路,而不是创建一个简单且规范化的模式。

为什么您如此担心放入无意义的键?你会让用户以非标准方式访问这些数据吗?

于 2010-04-02T19:12:47.593 回答