数据库规范化设计 - 单个或多个表

时间:2010-07-12 13:16:42

标签: performance database-design readability maintainability

这应该在数据库中表示为1个表还是3个表?我和我的朋友对此有不同的看法,所以我希望看到对此的一般看法。 (也许它应该是对任何一种解决方案的投票?)

Create Table Order
// Basic fields of the table
 - ID (Primary key)
 - CustomerID  (integer, with a FK)
 - Quantity
 - ProductID  (integer, with a FK)

 // Then depending on user selection, either these fields need to be specified 
 // (could be factored out to a separate table):
 {
 - InternalAccountID (integer, with a FK)
 - InternalCompanyID (integer, with a FK)
 }

 // Or these (could be factored out to a separate table):
 {
 - ExternalAccountNumber (free text string)
 - ExternalCompanyName (free text string)
 - ExtraInformation (free text string)
 }

1表格方法:

优点:

  • 性能(一个插入而不是两个,FK检查,无连接)
  • 可能占用更少的空间(额外的表有开销+索引+额外的ID字段)
  • 一张桌子而不是三张
  • 几乎没有理由分成2 + 3个字段的新表(或什么?)

缺点:

  • Nullable fields
  • 潜在的额外“类型”列(可以跳过)
  • 打破3NF(?)

请求优点和缺点以及个人意见。 :)

编辑:我尝试使用与实际使用不同的实体来简化示例,因此任何改变模型的建议都不会对我有所帮助。即请关注技术方面而不是领域模型。

7 个答案:

答案 0 :(得分:3)

我的意见是,如果

 // Then depending on user selection, either these fields need to be specified 
 // (could be factored out to a separate table):
 {
 - InternalAccountID (integer, with a FK)
 - InternalCompanyID (integer, with a FK)
 }

 // Or these (could be factored out to a separate table):
 {
 - ExternalAccountNumber (free text string)
 - ExternalCompanyName (free text string)
 - ExtraInformation (free text string)
 }

始终是1:1的订单(即,您不能有3个帐户ID),然后将其保留为一个表。要处理您的null问题,您可以添加一个名为InternalCustomer(boolean)或CustomerType(varChar)的列,您可以使用它来定义内部或外部客户,以了解您应该查看哪两个字段集中的哪一个特定客户。

由于我们不知道完整使用此数据或整个数据库的模式,因此对此的任何响应都不能完全合格。

答案 1 :(得分:3)

希望这是不言自明的。

order_model_v1

答案 2 :(得分:0)

如果您想避免数据重复,您应该使用2或3表解决方案。例如,如果Order表中有External列,则值可能会多次存在。如果数据如下所示:

ID   ExternalCompanyName
1    ACME
2    ACME
3    My Company
4    ACME

现在,如果ACME将名称更改为ACME,Inc。,则必须更新许多行。如果表是规范化的,外部公司在一个单独的表中,您将更新一行。请注意,可能存在将帐号放在其自己的表中的参数,但我们将其保留为极端规范化。

订单与公司/帐户之间似乎不是一对一关系,除非每个公司/帐户只能有一个订单。这听起来更像是一对多的关系。

现在,如果在单表环境中更新ExternalCompanyName时出错,并且只有部分行得到更新,会发生什么。你有一些使用ACME的行和一些使用ACME,Inc的行。你最终会出现数据不佳的情况。

此外,如果这真的是一对多的关系,那么你真的不会节省空间。您正在按顺序复制数据,而不是将其存储在另一个表中。

答案 3 :(得分:0)

随着音量的增加两个表的选择可能比一个快得多。有时,这种重构(分区)是在成熟的数据库上完成的,以提高性能。

想象一下,将此用于多表连接,其中某些条件在此表中,但其他条目位于不同的表中。

select from order join customer using (customer_id)
where
    order.order_date between ? and ?
    and customer.name = ?

最终可能会从磁盘中获取日期的所有order行,然后将其中的许多行丢弃,因为它们与连接不匹配。从磁盘获取的速度很快,可能会破坏您的RAM缓存。

select from order join order_detail using (order_id) join customer using (customer_id)
where
    order.order_date between ? and ?
    and customer.name = ?

在这种情况下,当它从磁盘加载所有order行时,它不会像以前那样受到伤害,因为表格更窄更小。它不需要加载与过滤无关的所有冗长字段。最后,加入customer后,它只会获取符合所有条件的order_detail行。

如果您希望这个很大,则应考虑拆分表,以便对搜索最关键的字段位于一个表中,而“data”字段位于其他一对一表中。 / p>

底线是:普通形式和域名是一回事,但性能通常需要权衡。您可以隐藏其中一些(用视图覆盖拆分),但不是全部(为了更快的选择而重复/聚合字段)。

答案 4 :(得分:0)

我绝对使用3表解决方案。通过将这些数据分成3个表,您实际上不能让任何查询返回完整的订单头而不加入外键,并且每个新订单的插入都会更新多个表和索引,这是并发问题。我建议使用2个表,一个用于InternalOrders,一个用于ExternalOrders。对于需要对来自两组订单的数据进行综合查询的情况,请定义一个视图,该视图是两个表的并集。

惊讶地看到产品ID和数量是订单标题的一部分。我见过的每个订单跟踪数据库都将订单项作为单独的表分开,使用订单ID作为外键,这样一个订单可以包含多个产品(或者具有不同数量,交货时间等的相同产品)。 )。

答案 5 :(得分:0)

我不是纯粹主义者,所以当它有意义时,3nf是好的......但你不必理所当然地认为它总会如此。

从务实的角度来看,你的目标是什么?您的利弊列表是一个良好的开端。我会在列表中添加一些想法 - 正如您认为合适的那样。

1)数据库中的任何其他表是否需要与此数据相关(例如,加入)?这就是RDB的重点。

2)您的数据库会增长吗?即使一张表现在有意义,它总是有意义的吗?你会后悔的,如果你发现自己想要添加更多的表,并且你的非规范化表强迫你“解决”它,处理返回的额外行,执行时间较慢等等。

3)当您的客户获得新的外部帐户或您有什么时,会发生什么。你会创造一个全新的记录吗?您如何回答诸如“什么是客户某某的账号?”等问题。

...

我认为一般来说,我选择可扩展,在这种情况下可能意味着3nf。 1表在一个非常狭窄的范围内更容易处理,但如果有任何变化,你将处理“如何将此表拆分为正确相关的3nf表,而不会弄乱所有已创建的依赖关系它?”。那个没有乐趣。

答案 6 :(得分:0)

在他可以订购之前是否与客户相关联的帐户信息(即您有另一张表,您可以在其中跟踪客户ID可以使用的帐户ID)?您是否可以将所有帐户抽象为一个合理统一的模式(一个可以有一些空值),因为您有一个通用的AccountId(代理键),然后Account表中有3个varchar字段,另一个跟踪帐户的类型(使用过)用于计费等)?

如果您可以这样做,那么您的订单只跟踪一个AccountID,因为订单(作为实体)实际上并不关心使用哪种付款方式 - 它只关心它是该用户的合法/现有/批准的AccountId。其他一切都是别人的业务,可以这么说(计费或检查资金等),而且无论如何,它的处理都需要更多数据。

这可以保持您的订单清洁无效,并且也可以帮助分离关注点。

从概念上讲,您的订单实际上是所谓的事实表 - 仅包含数字和FK-s,项目大小较小但数量较多。

所以:

 Table Order (
     - OrderId
     - Quantity
     - ProductId
     - DiscountId -- sonner or latter :-)
     - AccountId
     - PaymentStatus -- probaly FK as well or predefined constant
 )

 Table Account (
     - AccountId
     - BillingInfo  -- akka ext acct number as text
     - PrincialName -- akka ext company name, some equivalent for internal acct-s
     - AdditionalData
 )