何时归一化数据库设计

Mar*_*ans 48 sql database-design normalization denormalization

我知道在Stack Overflow上已经广泛讨论了normalis(z)ation.我读过很多以前的讨论.我有一些额外的问题.

我正在研究一个至少有100个表的遗留系统.数据库具有一些非规范化结构,包含各种不同数据的表以及其他问题.我被赋予了改善它的任务.我不能再重新开始,但需要修改现有架构.

在过去,我一直试图设计规范化的数据库.现在的问题.一位高级开发人员建议在某些情况下我们无法规范化:

1)使用时态数据.例如,创建了一个链接到产品的发票.如果客户在一年后要求提供此发票的副本,我们必须能够生成原件的精确副本.如果产品价格,名称或描述已更新怎么办?资深人士建议将价格和其他产品信息复制到发票表中.我想也许我们应该有另一个表,例如productPrice,它有一个日期字段,所以我们可以跟踪价格随时间的变化.我想对产品描述和名称需要相同的东西吗?看似复杂.你怎么看?

2)数据库是一个会计系统.我对会计不是很熟悉.目前,一些摘要数据被导出并存储在数据库中.例如,当年的总销售额.我的高级助理表示,会计师喜欢通过将此值与实际根据发票等计算的数据进行比较来检查事情是否正确,以使他们相信应用程序正常工作.他说,目前我们可以判断是否有人错误地从去年删除了发票,因为总数不会相同.他还指出,在飞行中计算这些总数可能会非常缓慢.当然,我说数据不应该重复,应该在需要时进行计算.我建议我们可以使用SQL Reporting Services或其他一些解决方案来隔夜生成这些报告并缓存它们.无论如何,他不相信.对此有何评论?

非常感谢:)
干杯
马克

编辑

感谢您的出色回应.遗憾的是我只能将其中一个作为答案,因为这里有很多好的建议.

Per*_*DBA 46

您的高级同事是开发人员,而不是数据建模人员.如果没有它们,你最好从头开始.规范化只对那些不会阅读书籍,并从维基百科的业余爱好者那里获得"知识"的人来说很复杂.他让你思考是公平的,但有些问题是荒谬的.

你的号码:

  1. 您需要了解实际在线数据与历史数据之间的差异; 那么仅仅是历史和档案需求之间的区别.所有这些都是针对特定业务需求的,对所有其他人来说都是错误的,没有普遍的是非.

    • 为什么没有纸质副本的发票?在大多数法律和税收要求的国家,捕获旧发票的难度究竟是什么?
    • 如果数据库需要存储已关闭的发票,那么一旦发票结束,您就需要一种捕获该信息的方法.
    • ProductPrice(实际上,我会称之为ProductDate)是一个好主意,但可能没有必要.但是你是对的,你需要在整个数据库的完整上下文中评估数据的货币.
    • 我看不出如何将产品价格复制到发票表会有所帮助(不是很多行项目?)
    • 在现代数据库中,发票的副本需要反刍,封闭的发票另外以不同的形式存储,例如XML.一位客户将PDF保存为BLOB.因此,五年前的产品价格并没有搞乱.但基本发票数据是在线和最新的,即使是已结束的发票; 你不能用当前价格重新计算古代发票.
    • 有些人使用archive_invoice表,但这有问题,因为现在每个代码段或用户报表工具都必须在两个地方查看(请注意,这些天有些用户比大多数开发人员更了解数据库)
    • 无论如何,这是所有讨论,供您理解.我30年来写的数据库都没有遇到过这类问题,而且所有数据库都符合法律和税收要求.
      • 数据库从一组表中提供当前和存档目的(没有"存档"表)
      • 创建发票后,它是一份法律文件,不能更改或删除(可以通过新的发票撤销或部分记入,具有负值).他们被标记了IsIssued/IsPaid/Etc
      • Products 无法删除,可以标记 IsObsolete
      • InvoiceHeader和InvoiceItem有单独的表
      • InvoiceItem两个InvoiceHeader和都有FKProduct
      • 由于许多原因(不仅仅是你提到的那些),InvoiceItem行包含NumUnits; ProductPrice; TaxAmount; ExtendedPrice.当然,这看起来像一个"非正规化",但它不是,因为价格,税率等可能会发生变化.但更重要的是,法律要求是我们可以按需复制旧发票.
      • (可以从纸质文件中复制,这不是必需的)
      • InvoiceTotalAmount是一个派生列,只是SUM()InvoiceItems的列
        .
  2. 那是垃圾.会计系统和会计师不会那样"工作".

    • 如果它是真正的会计系统,那么它将具有JournalEntries或"double entry"; 这是一个合格的帐户需要使用(法律).

      • 双重输入并不意味着重复输入; 这意味着每笔金融交易(一笔金额)应具有适用的源账户和目标账户; 所以没有"非规范化"或重复.在银行数据库中,由于金融交易是针对单个账户的,因此通常在一个Db交易中呈现为两个单独的金融交易(行).普通商业数据库约束用于确保每个金融交易有两个"方面".
        .
    • 确保发票不可删除是一个单独的问题,与安全性等有关.如果有人对从数据库中删除的东西感到妄想,并且他们的数据库没有合格的人保护,那么他们就会遇到更多不同的问题.与这个问题无关.获得安全审计,并做他们告诉你的任何事情.

    • 这个网站上有一些人认为维基是一个你可以学习的地方.事实并非如此.它是业余爱好者写的"定义"的污水池,其他业余爱好者不断改变"定义".没有可以依赖的固定定义.所以不要担心维基说什么或人们说维基说什么,他们提到维基的那一刻,你知道他们的"知识"来自阅读不合格; 而他们正在阅读的是一个不断变化的污水坑.他们可以预见地争论"定义",因为他们没有实际经验; 经验丰富的人将继续这份工作

    • 规范化数据库总是比非规范化数据库快得多.因此,了解Normalization和Denormalisaion是什么以及它不是什么非常重要.当人们拥有流畅的和业余的"定义"时,这个过程就会受到很大的阻碍,这只会导致混乱和浪费时间的"讨论".当您有固定的定义时,您可以避免所有这些,并继续工作.

    • 为了节省时间和处理能力,重新计算不会改变的信息,例如:每年的YTD总数,但是今年,汇总表非常正常; 今年的每月MTD总计,但不是本月.当(a)信息非常大且(b)不变时,"总是重新计算"数据有点傻.仅计算当前月份

      • 在银行系统(每天数百万笔交易)中,在EndOfDay,我们也计算并存储每日总计.这些都被覆盖了过去五天,因为奥迪克斯正在进行更改,并且允许JournalEntries对过去5天的金融交易进行更改.
      • 非银行系统通常不需要每日总计
        .
    • 汇总表不是"非规范化"(除了那些刚刚从他们神奇的,不断变化的流动"源"中学习"正常化"的人的眼睛;或者作为非实践者,他们应用简单的黑白规则一切).同样,这里没有讨论这个定义; 它根本不适用于摘要表.

    • 汇总表不会影响数据完整性(当然,假设它们来自的数据是完整的).

    • 摘要表是对数据库的补充,不需要与数据库具有相同的约束.主要是报表或数据仓库表,而不是数据库表.

    • 没有与Summary表相关的Update Anomalies(这是一个严格的定义).您无法更改或删除去年的发票.更新异常适用于真正的非规范化或非规范化的当前数据.

  • 大多数答案都是有帮助的,但我强烈反对"标准化数据库总是比非标准化数据库快得多"的说法.即使我不使用"总是",这显然也是假的.在许多情况下,数据库的选择性,连贯的非规范化可以导致极端的性能改进.如果您事先能够知道数据库将接收哪些复杂,耗时的查询,则可以预先计算这些查询的结果 - 例如,将14表连接替换为已包含所需数据的表. (33认同)
  • 一般来说,你的答案非常好,非常有用; 有几个部分是有争议的,但有一部分是完全没用的,而且大多是错误的.这是一个关于维基的部分 - 你设法收集的矛盾数量非常有趣 - 我只会挑出一个论点:"*他们的'知识'来自阅读而不是资格.*".它表明你会将阅读作为低等知识的论据.(!)(我不会详细为wiki辩护;它对某些目的很有用,对其他目的来说可能不合标准;只要它引用引用...... (16认同)
  • ..并没有省略概念的主要领域,它主要是*有用*).你错误地概括了作者和过程.你错误地概括了引用维基百科的人 - 例如我引用维基百科,因为它可供所有人使用,而不是因为它是我知识的主要来源; 我也引用它,因为它通常是指其他作品,因此很容易追踪索赔.此外,我试图评估维基百科的报价和一般质量.我希望这些评论能够帮助您了解和纠正您在这一点上所采用的主观性. (12认同)
  • 哇 - 这就是我所说的答案!很高兴我不支付你的小时费率.这是坚实的黄金.我觉得我现在真的开始明白了这一切.谢谢PerformanceDBA :) (10认同)
  • -1必须同意@ChrisJohnson上面的评论.太多的过度概括和广泛的假设,缺乏对所涉及的错综复杂的理解,以及在这里有点不合适的膨胀态度.哦,大多数DW都是非规范化的,有一个理由和地方. (8认同)
  • @PerformanceDBA,在这种情况下'技术'基础是纯逻辑.你错误地概括道:"这个网站上有一些人认为维基是一个你可以学到东西的地方.事实并非如此." 这个"结论"在逻辑上没有任何价值,这是一种观点.我可以尝试(!)反驳这样的一个:1)阅读http://en.wikipedia.org/wiki/Ad_hominem 2)如果你接受那篇文章的参考文献和质量,你会学到一些东西*并且会相互矛盾你自己的说法.*=论证ad hominem是合乎逻辑的(和修辞)*谬误*(!) - 在技术讨论中没有位置. (3认同)
  • @不合理.请确定您所掌握的技术基础,资格和经验,例如"你错了......".否则,这些陈述很冗长但是空洞. (2认同)
  • 不赞成这种说法:“如果没有它们,您最好从头开始。只有那些不会读书的人,才能从Wiki上的业余爱好者那里获得'知识',规范化才很复杂。”否则,还可以。我们中有些人不需要书... (2认同)

Jef*_*and 9

1)这是一个档案.其中的所有内容都不应该更新.我会考虑老人的建议,让发票表自成一格.也许使用blob作为包含标记语言的发票本身?

2)报告服务,一个触发更新的仓库表,你用脚本构建的东西......我认为这些都可以.确实是理想化的,但并不总是很快.我有一个很好的医疗数据库,我管理的数据库是完全标准化的...然后有一系列非标准化的表格,包括卷起的方程式和通常拉取的字段.几乎所有东西都是从那个非规范化的集合中运行的 - 加载文件时用触发器附加到这些文件比用每次我想要查看100,000条记录报告时必须从各种表中拉出来更快.


Unr*_*son 7

你提出了有效点,但你并不完全清楚规范化及其含义,例如

1)声称将发票保持为非正规化数据是完全错误的.让我们以价格为例 - 如果您的业务要求表明您必须保留价格历史,那么只保留当前价格是错误的并且它违反了要求.它与标准化无关,它设计得不好.非规范化是指在模型(和其他工件)中引入歧义的可能性 - 在这种情况下,您根本就不能正确地对问题空间进行建模.
在建模数据库以支持时态数据(或版本化和/或将数据库区域分为存档/时间和工作集)方面没有任何错误.

在不考虑语义(在需求方面)的情况下查看规范化是不可能的.

此外,如果您的高级开发人员无法看到差异,那么我猜他没有在RDBMS开发中获得资历;)

2)第二部分确实是非规范化.但是,如果你曾经遇到过认真宣扬规范化的高级数据库分析师,你会听到他/她说,只要你有意识地做到这一点,并且确保超重缺陷的好处以及异常不会让你感到厌恶,那么非规范化是完全可以接受的.他们还会告诉您规范化逻辑模型,并且在物理模型中,您可以出于各种目的(性能,维护等等)偏离理想模型.在我的书中,规范化的主要目的是让你没有隐藏的异常(例如参见5NF上的这篇文章)

即使在规范化的数据库上也允许缓存中间结果,甚至是规范化的最大传播者 - 你可以在应用层(作为某种缓存)进行缓存,或者你可以在数据库级别进行,或者你可以拥有一个数据仓库这样的目的.这些都是有效的选择,与规范化逻辑模型无关.

此外,至于你的会计师 - 你应该能够说服他,他所声称的不是一个好的测试并开发一套测试(可能与他一起),这将在没有用户干预的情况下自动化测试系统并给你更高的信心,您的系统是无bug的.

另一方面,我知道需要用户输入重复信息的系统,例如在输入实际行之前或之后输入发票上的行数,以确保输入完成.此数据是"重复的",如果您有一个验证输入的过程,则不必存储它.如果该程序稍后出现,则允许存储"非规范化"数据 - 再次,语义证明它是合理的,您可以将模型看作规范化.(包围这个概念是有益的)

编辑: 如果你看正常形式的正式定义,如果你认为一个设计非规范化,如果它破坏任何正常形式(对某些人来说,这很明显,没有),那么(2)中的术语"非规范化"是不正确的关于它的其他方式).

尽管如此,你可能还是想要习惯这样的想法:很多人而不是必要的无用文本会使用术语规范化来试图减少数据库中的冗余(例如,你会找到科学论文,通过我并没有说它们必须是正确的,正如警告它是常见的那样,调用派生属性是一种非规范化的形式,见这里).

如果你想引用一些更连贯和认可的权威(再次,并非所有人都认可),也许CJDate的话可以明确区分:

许多设计理论都与减少冗余有关; 规范化减少了relvars中的冗余,正交性减少了relvars中的冗余.

深入探讨数据库:从业者的关系理论

并在下一页

正如未能一直正常化意味着冗余并且可能导致某些异常,因此也不能坚持正交性.

因此,跨越relvars的冗余的正确术语是正交性(基本上所有正常形式都谈论单个relvar,因此如果你严格看待规范化,它将永远不会建议由于两个不同relvars之间的依赖性而导致的任何改进).

无论如何,当您考虑数据库设计时,其他重要概念之一也是逻辑和物理数据库模型之间的差异.在物理层面上有很多有用的东西,例如带有小计或索引的表格在逻辑模型中没有位置 - 您尝试建立和调查您尝试建模的概念之间的关系.这就是为什么你可以说它们是允许的并且它们不会破坏设计.

线条有时可能在什么是逻辑模型和什么是物理模型上有点模糊.特别好的例子是带小计的表格.要将其视为物理实现的一部分,并在逻辑级别上忽略它,您必须:

  • 确保用户(和应用程序)不能以与谓词不一致的方式直接更新小计表(换句话说,在小计过程中有错误)
  • 确保用户(和应用程序)无法更新这些依赖的表而不更新小计(换句话说,某些应用程序不会从详细信息表中删除行而不更新总计)

如果您违反上述任何规则,您将最终得到不一致的数据库,这将提供不一致的事实.(在这种情况下,如果你想正式设计一个程序来修复或检查所引起的问题,你不会认为它只是一个额外的表,它将存在于逻辑层面;它不应该存在).

此外,规范化始终取决于您尝试建模的语义和业务规则.例如,DBAPerformance提供了一个示例,其中TaxAmount在事务表中存储不是非规范化设计,但他没有提到它取决于您尝试建模的系统类型(这是显而易见的吗?); 例如,如果事务具有另一个被称为属性的属性,TaxRate那么它通常会被非规范化,因为对一组非关键属性存在功能依赖(TaxAmount = Amount*TaxRate => FD:Amount,TaxRate - > TaxAmount),其中一个应该是被删除或保证一致.

显然,您可能会说,但是,如果您构建的系统是针对审计公司的,那么您可能没有功能依赖性 - 他们可能正在审核使用手动计算或软件有缺陷或必须能够记录不完整数据的人员并且最初的计算可能是错误的,作为审计公司,您必须记录事实.

因此,由需求确定的语义(谓词)将影响是否有任何正常形式被破坏 - 通过影响功能依赖性(换句话说,正确建立功能依赖性是建立规范化数据库时建模的非常重要的一部分).

  • -1(2)不是“非规范化”。其余的,从这个错误的观念中产生的,也是不正确的。 (2认同)
  • @PerformanceDBA re 1)我要通过提及语义影响FD来“添加”您的答案,FD会影响NF的一致性。同样,您还没有谈论过这一点。您声称我有阅读问题与引用的文字无关。2)提供的示例说明了如何根据属性的语义来破坏或满足3NF的要求。这是完全正确的。请参阅上面的详细信息。 (2认同)
  • @PerformanceDBA,您是否真的知道论点是逻辑上的谬误?当然,可以将彼此的答案相加,实际上最好是重复一些内容,并且对一个主题的不同观点很有用。如果我说“如果您的高级开发人员看不到差异,那么我想他没有获得RDBMS开发的资历”,而您后来又说“您的高级同事是开发人员,而不是数据建模人员”,那么您将添加/完善已经说过的话。他人可能会做同样的事情并且是正确的(甚至是偶然的)。 (2认同)

use*_*421 5

我同意你的前辈关于(1)的看法。事务表行必须捕获事务时刻的整个状态。时期。你的建议没有记录实际数据,所以它是不可接受的。我也同意(2)。无论企业通过交叉检查的方式想要什么,您都必须实施。会计基于交叉检查、复式记账、汇总分类账等。您必须这样做。这是非常基本的,您甚至不应将其视为非规范化,就像实现业务需求一样。

  • 是的,我想我太迂腐了。感谢您对会计系统 EJP 的深入了解。 (2认同)

nvo*_*gel 5

1) 不需要非规范化。您只需要确定您需要的每个更改的详细程度,并使用适当的键保持它。

2)与非规范化无关。存储摘要数据不会使数据库非规范化。将非关键属性派生的结果存储在同一个表中将是非规范化的一个例子,但这似乎不是你在这里谈论的内容。

  • @无理。大卫是对的。Wiki 是一个污水池,今天不值得讨论它所说的内容。汇总表是速度的**附加**表;它们不是标准化数据库的一部分。去年发票总额没有更新异常(今天有,不应该总结)。5NF,顾名思义,不管维基本周怎么说,零更新异常。他们仍在弄清楚“更新异常的定义”。 (3认同)
  • 你在断章取意。这些是 Codd 关于规范化的评论,但它们不是非规范化的定义。我很想看到一个没有更新异常的非平凡数据库的例子。我敢说这几乎是可能的,但据我所知这是不可能证明的,因为没有正式的方法来识别冗余。Date/McGoveran 提出了一个经典的例子。一对叫做爱与恨的关系。它们是“规范化的”(6NF),但也有可能出现数据修改异常,因为它们是相互排斥的。 (3认同)

小智 5

您的高级开发人员提出了非常有效的观点。我自己通过服务不会对历史数据进行非规范化的系统来艰难地学到了这些。

从某种意义上说,它并没有真正给数据库增加任何开销。您正在从数据库中的现有数据创建发票表。发票是时间的快照。对生成发票所需的信息进行非规范化可以使您的报告变得更加容易。当您需要生成新报告并希望快速完成时,您会喜欢去规范化。

就数据库中的总数而言。之前当我对一个应用程序进行更改导致数字的加法方式不同(不像您想象的那么难)时,这已经救了我的屁股。在实时应用程序中,总数为我提供了一个明确的返回位置,以纠正差异。我以前写过这个,你可以在这里阅读:http : //jlrand.com/?p=95