1

我正在用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,不为null),就像我只会使用自然键和连续字符一样,然后添加一个组合外键。

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

这妥善限制了LineItem到一个单一的公司,但它似乎过度设计和错误的。 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 

不需要其它表自然外键,但它仍然是在焦炭在接合时有可用的整数。

有没有干净的方式执行与外国键域约束像上帝打算,但在代理人的情况下,头也不回的模式和索引到一个复杂的烂摊子?

回答

1

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

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

我要保护的数据的努力,但成本可能增长,因为你得到更多的数据,并肯定是有更复杂的数据模型的额外负担是这样高。

我认为通过应用程序中的'自然'使用足以保护数据。当您开始创建新发票时,如果您正在查找要发票的产品,那么您将使用相同的公司ID来搜索与创建发票相同的产品。如果你不知何故在那里得到错误的ID(错误的代码,直接在DB中的用户),那么不好的结果将立即显现。

我不太了解您的帖子的第二部分。它似乎就像你试图走下一条复杂的路径,而不是创建一个简单的规范化模式。

为什么你非常担心非感应键被放入?你会让用户以非标准的方式访问这些数据吗?