处理“超标准化”数据

时间:2009-02-03 15:53:29

标签: sql ruby-on-rails database-design denormalization normalizing

我的雇主,一家小型办公用品公司,正在转换供应商,我正在查看他们的电子内容,以提出一个强大的数据库架构;我们以前的模式几乎完全没有任何想法,而且几乎导致了一个无法忍受的数据模型,信息损坏,信息不一致。

新供应商的数据比旧供应商的数据要好得多,但他们的数据是我称之为超标准化的。例如,他们的产品类别结构有5个级别:Master Department,Department,Class,Subclass,Product Block。此外,产品块内容具有产品的长描述,搜索术语和图像名称(这个想法是产品块包含产品和所有变体 - 例如特定笔可能有黑色,蓝色或红色墨水;所有这些项目基本上是相同的,所以它们适用于单个产品块)。在我给出的数据中,这表示为产品表(我说“表”,但它是带有数据的平面文件),其中引用了产品块的唯一ID。

我正在尝试提供一个强大的架构来容纳我提供的数据,因为我需要相对较快地加载它,并且他们给我的数据似乎与类型不匹配他们在样本网站(http://www.iteminfo.com)上提供的数据。无论如何,我不打算重复使用它们的表示结构,所以这是一个没有实际意义的点,但我正在浏览网站以获得有关如何构建事物的一些想法。

我不确定的是我是否应该以这种格式保存数据,或者例如使用自引用关系将Master / Department / Class / Subclass合并到单个“Categories”表中,并链接产品块(产品块应该保持独立,因为它不是“类别”本身,而是一组给定类别的相关产品)。目前,产品块表引用了子类表,因此如果我将它们合并在一起,这将更改为“category_id”。

我可能会创建一个电子商务店面,利用Ruby on Rails中的这些数据(或者这是我的计划,无论如何),所以我试图避免以后遇到困难或者有一个膨胀的应用程序 - 也许我在考虑太多,但我宁愿安全而不是抱歉;我们以前的数据真是一团糟,由于数据不一致和不准确,使公司损失了数万美元。此外,我将通过确保我的数据库是健壮的并强制执行约束(我计划在应用程序级别执行它)来稍微摆脱Rails约定,所以这也是我需要考虑的事项。

你会如何解决这样的情况?请记住,我已经将数据加载到模拟表结构的平面文件中(我有文档说明哪些列是哪些列和设置了哪些引用);我正在试图决定是否应该像现在这样将它们保持正常化,或者我是否应该寻求巩固;我需要知道每个方法将如何影响我使用Rails对网站进行编程的方式,因为如果我进行整合,单个表中基本上会有4个“级别”的类别,但这似乎比单独的表更易于管理每个级别,因为除了Subclass(直接链接到产品块)之外,他们不会除了显示其下一个级别的类别之外的任何内容。对于处理这样的数据的“最佳”方式,我总是感到茫然 - 我知道“正常化直到它受到伤害,然后反正规化直到它起作用”这句话但是我从来没有真正实现它直到现在。

10 个答案:

答案 0 :(得分:6)

我更喜欢非正规数据模型的“超标准化”方法。您提到的自引用表可能会减少表的数量并在某些方面简化生命,但通常这种类型的关系可能很难处理。分层查询变得很痛苦,将对象模型映射到此(如果您决定走这条路线)。

一些额外的连接不会受到伤害并且会使应用程序更易于维护。除非由于连接数量过多导致性能下降,否则我会选择保留原样。如果任何这些级别的表需要添加额外的功能,那么您将不会遇到问题,因为您将它们全部合并到自引用表中。

答案 1 :(得分:3)

我完全不同意关于父子层次结构的自引用表结构的批评。在大多数情况下,链表结构使UI和业务层编程更容易,更易于维护,因为链表和树是用UI和业务层通常实现的语言表示这些数据的自然方式。

对于在这些结构上维护数据完整性约束的难度的批评是完全有效的,尽管简单的解决方案是使用托管更难检查约束的闭包表。闭包表可以通过触发器轻松维护。

在DB(闭包表和触发器)中,权衡是一个额外的复杂性,因为UI和业务层代码的复杂性要低得多。

答案 2 :(得分:2)

如果我理解正确,你想把它们分开的表格转换成一个层次结构,这个层次结构保存在一个带有自引用FK的表中。

这通常是一种更灵活的方法(例如,如果要添加第五级),但是SQL和关系数据模型不能很好地处理这样的链接列表,即使使用像MS SQL这样的新语法也是如此服务器CTE。不可否认,CTE让它变得更好。

执行某些事情可能既困难又昂贵,例如产品必须始终位于层次结构的第四层等等。

如果您决定这样做,那么一定要查看Joe Celko的SQL for Smarties,我相信它有一两个关于建模和使用SQL中的层次结构的一两部分或更好地得到他的专着的书到主题(Joe Celko's Trees and Hierarchies in SQL for Smarties)。

答案 3 :(得分:2)

Normalization表示数据完整性,即:每个普通表单都会减少数据不一致的情况。

作为一项规则,denormalization的目标是querying更快,但会增加空间,增加DML时间,最后但并非最不重要的是,增加了使数据保持一致的努力

通常可以更快地编写代码(编写速度更快,代码编写速度更快),如果数据为normalized,则代码不易出错。

答案 4 :(得分:2)

自我引用表几乎总是变得更糟糕,查询和执行比标准化表更差。不要这样做。它可能会让你看起来更优雅,但它不是,而且是一种非常糟糕的数据库设计技术。就个人而言,你描述的结构对我来说听起来不错,不是超标准化的。正确规范化的数据库(具有外键约束以及默认值,触发器(如果复杂规则需要)和数据验证约束)也更有可能获得一致和准确的数据。我同意数据库强制执行规则,这可能是最后一个应用程序有错误数据的部分原因,因为规则没有在适当的地方强制执行,人们可以轻松绕过它们。并不是说应用程序也不应该检查(即使发送无效日期,例如数据库在插入时失败也没有意义)。由于您需要重新设计,我会花费更多的时间和精力来设计必要的约束并选择正确的数据类型(例如,不将日期存储为字符串数据),而不是试图使完美的普通规范化结构看起来更优雅。

答案 5 :(得分:1)

我会尽可能接近他们的模型(如果可能的话,我会得到与他们的架构匹配的文件 - 而不是扁平版本)。如果您将数据直接引入模型,如果他们发送的数据开始打破转换到内部应用程序模型的假设会发生什么?

最好将数据输入,运行健全性检查并检查是否违反了假设。然后,如果您确实拥有特定于应用程序的模型,请将其转换为适合您的应用程序的最佳模型。

答案 6 :(得分:0)

不要反规范化。尝试通过非规范化来实现良好的模式设计就像试图从纽约开车去旧金山。它没有告诉你走哪条路。

在您的情况下,您想要弄清楚规范化架构的内容。您可以在很大程度上基于源模式,但您需要了解数据中的功能依赖(FD)。无论是源模式还是扁平化文件都不能保证显示所有FD。

一旦了解规范化架构的外观,您现在需要弄清楚如何设计满足您需求的架构。它的架构有点不完全规范化,所以就这样吧。但要准备好在编译展平文件中的数据与您的设计模式中的数据之间的转换时遇到困难。

你说你公司以前的模式由于不一致和不准确而花费了数百万美元。您的架构规范化程度越高,您对内部不一致性的保护程度就越高。这使您可以更自由地对不准确性保持警惕。始终错误的一致数据可能与不一致的数据一样具有误导性。

答案 7 :(得分:0)

是你的店面(或者你正在建造什么,不是很清楚)总是会使用来自这个供应商的数据?您是否可以更换供应商或添加其他不同的供应商?

如果是这样,请设计满足 需求的通用架构,并将供应商数据映射到它。就个人而言,我宁愿忍受自我引用类别(层级)表的(难以置信的次要)“痛苦”而不是维持四个(显然是半无用的)类别变体,然后明年发现他们已经添加了第5个,或推出仅有三个产品系列......

答案 8 :(得分:0)

对我来说,真正的问题是:更适合模特的是什么?

这就像比较一个元组和一个列表。

  1. 元组是一个固定的大小并且是异构的 - 它们是“超标准化的”。
  2. 列表是一种任意大小并且是同质的。
  3. 当我需要一个Tuple和一个List时,我需要一个Tuple,当我需要一个列表时;它们从根本上服务于不同的目的。

    在这种情况下,由于产品结构已经很好地定义了(我假设不太可能改变),那么我会坚持使用“元组方法”。 List(或递归表格模式)的实际功能/用途是当您需要扩展到任意深度时,例如BOM或家谱树。

    我根据需要在我的某些数据库中使用这两种方法。但是,还存在递归模式的“隐藏成本”,即并非所有ORM(不确定AR)支持它。许多现代数据库都支持“连接”(Oracle),层次结构ID(SQL Server)或其他递归模式。另一种方法是使用基于集合的层次结构(通常依赖于触发器/维护)。在任何情况下,如果使用的ORM不能很好地支持递归查询,则可能存在直接使用数据库功能的额外“成本” - 无论是在手动查询/视图生成还是管理(如触发器)方面。如果您不使用时髦的ORM,或仅使用iBatis等逻辑分隔符,则此问题可能甚至不适用。

    就性能而言,在新的Oracle或SQL Server(以及可能的其他)RDBMS上,它应该是非常可比的,因此这是我最不担心的问题:但请查看可用于您的RDBMS和可移植性问题的解决方案。

答案 9 :(得分:0)

每个建议您不要在数据库中引入层次结构的人,只考虑具有自引用表的选项。这不是在数据库中建模层次结构的唯一方法。 您可以使用不同的方法,在不使用递归查询的情况下为您提供更轻松,更快速的查询。 假设您的层次结构中有大量节点(类别):

  

Set1 =(Node1 Node2 Node3 ...)

此集合中的任何节点也可以是其自身的另一个节点,其中包含其他节点或嵌套集:

  

Node1 =(Node2 Node3 =(Node4 Node5 =(Node6)Node7))

现在,我们如何建模?让每个节点有两个属性,设置它包含的节点的边界:

  

Node = {Id:int,Min:int,Max:int}

为了模拟我们的层次结构,我们只需相应地分配这些最小/最大值:

  

Node1 = {Id = 1,Min = 1,Max = 10}
  Node2 = {Id = 2,Min = 2,Max = 2}
  Node3 = {Id = 3,Min = 3,Max = 9}
  Node4 = {Id = 4,Min = 4,Max = 4}
  Node5 = {Id = 5,Min = 5,Max = 7}
  Node6 = {Id = 6,Min = 6,Max = 6}
  节点7 = {Id = 7,Min = 8,Max = 8}

现在,查询Set / Node5下的所有节点:

  

从节点中选择n。*为n,节点为s
  其中s.Id = 5且s.Min < n.Min和n.Max&lt; s.Max

唯一耗费资源的操作是,如果要插入新节点,或者移动层次结构中的某个节点,因为许多记录都会受到影响,但这很好,因为层次结构本身不会经常更改。