48

我知道标准化已经在 Stack Overflow 上进行了广泛的讨论。我已经阅读了许多以前的讨论。不过,我还有一些其他问题。

我正在开发一个至少有 100 个表的遗留系统。数据库存在一些非规范化的结构、包含各种不同数据的表以及其他问题。我被赋予了尝试改进它的任务。我不能只是重新开始,而是需要修改现有架构。

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

  1. 有时间数据。例如,创建链接到产品的发票。如果客户在一年后要求提供此发票的副本,我们必须能够提供原件的准确副本。如果产品价格、名称或描述已更新怎么办?大佬建议将价格等产品信息复制到发票表中。我在想也许我们应该有另一个表,比如 productPrice ,它有一个日期字段,这样我们就可以跟踪价格随时间的变化。我猜我们需要相同的产品描述和名称吗?看起来很复杂。你怎么看?

  2. 数据库是一个会计系统。我对会计不是很熟悉。目前,一些汇总数据被导出并存储在数据库中。例如当年的总销售额。我的高级助理说会计师喜欢通过将该值与实际从发票等计算的数据进行比较来检查事情是否正确,以使他们相信应用程序正常运行。

他说,例如,目前我们可以判断是否有人错误地删除了去年的发票,因为总数不会相同。他还指出,动态计算这些总数可能会很慢。当然我说过数据不应该重复,应该总是在需要的时候计算。我建议我们可以使用 SQL Reporting Services 或其他可以在一夜之间生成这些报告并缓存它们的解决方案。反正他不信。对此有何评论?

4

9 回答 9

50

您的高级同事是开发人员,而不是数据建模人员。没有它们,你最好从头开始。规范化只对那些不读书的人来说是复杂的。他让你思考是很公平的,但有些问题是荒谬的。

你的号码:

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

    • 为什么没有纸质发票?在大多数国家,这将是法律和税收要求,找出旧发票到底有什么困难?
    • 如果数据库需要存储已关闭的发票,那么可以肯定的是,一旦发票关闭,您就需要一种捕获该信息的方法。
    • ProductPrice(实际上,我会称之为ProductDate)是一个好主意,但可能没有必要。但是您是对的,您需要在整个数据库的完整上下文中评估数据的流通性。
    • 我看不出将产品价格复制到发票表会有什么帮助(不是有很多行项目吗?)
    • 在需要返还发票副本的现代数据库中,已关闭的发票另外以不同的形式存储,例如 XML。一位客户将 PDF 保存为 BLOB。所以五年前的产品价格是没有问题的。但是基本的发票数据是在线的和最新的,即使是关闭的发票;您只是无法使用当前价格重新计算旧发票。
    • 有些人使用 archive_invoice 表,但这有问题,因为现在每个代码段或用户报告工具都必须在两个地方查看(请注意,现在有些用户比大多数开发人员更了解数据库)
  • 无论如何,这都是讨论,以供您理解。现在是技术意图。
    • 数据库从一组表(没有“归档”表
    • 一旦创建发票,它就是一个法律文件,不能更改或删除(它可以被新发票冲销或部分贷记,具有负值)。它们被标记IsIssued/IsPaid/Etc
    • Products不能删除,可以标记IsObsolete
    • InvoiceHeader 和 InvoiceItem 有单独的表
    • InvoiceItem对两者都有 FKInvoiceHeader并且Product
    • 由于许多原因(不仅是您提到的那些),InvoiceItem 行包含NumUnits; ProductPrice; TaxAmount; ExtendedPrice. 当然,这看起来像是“非规范化”,但事实并非如此,因为价格、税率等可能会发生变化。但更重要的是,法律要求是我们可以按需复制旧发票。
    • (如果可以从纸质文件中复制,则不需要)
    • InvoiceTotalAmount是一个派生列,只是SUM()InvoiceItems
  1. 那是垃圾。会计系统和会计师不会那样“工作”。

    • 如果它是一个真正的会计系统,那么它就会有 JournalEntries,或者“复式记账”;这就是(根据法律)要求合格帐户使用的内容。

    • 复式会计并不意味着重复分录;这意味着每笔金融交易(一个金额)都应有一个适用的源账户和目标账户;所以没有“非规范化”或重复。在银行数据库中,由于金融交易针对单个账户,因此通常在一个 Db 交易中呈现为两个单独的金融交易(行)。普通的商业数据库约束用于确保每个金融交易都有两个“方面”。

    • 确保发票不可删除是一个单独的问题,与安全性等有关。如果有人对从他们的数据库中删除的东西感到偏执,并且他们的数据库没有由合格的人保护,那么他们就会遇到更多不同的问题与这个问题无关。获得安全审计,并按照他们告诉你的去做。

    • 维基百科不是任何技术主题的可靠信息来源,更不用说规范化了。

    • 规范化数据库总是比非规范化数据库快得多
      所以了解什么是规范化和非规范化是非常重要的,什么不是。当人们有流动的和业余的“定义”时,这个过程会受到很大的阻碍,只会导致混乱和浪费时间的“讨论”。当你有固定的定义时,你可以避免所有这些,然后继续工作。

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

      • 在银行系统(每天数百万笔交易)中,在 EndOfDay,我们也计算和存储每日总计。这些在过去 5 天被覆盖,因为审计员正在进行更改,并且允许针对过去 5 天的财务交易的 JournalEntries。
      • 非银行系统一般不需要每日总计
    • 汇总表不是“非规范化”(除非在那些刚刚从其神奇的、不断变化的流体“源”中了解“规范化”的人眼中;或者作为非实践者,他们应用简单的黑白规则对一切)。同样,这里没有讨论定义;它根本不适用于汇总表。

    • 汇总表不影响数据完整性(当然假设它们的来源数据是完整的)。

    • 汇总表是对当前数据的补充,不需要与当前数据具有相同的约束条件。与当前数据表相反,本质上存在报告表或数据仓库表。

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


回应评论

所以为了存档可以进行非规范化吗?

我上面的解释似乎不够清楚。让我们看一个例子,并比较这些选项。

富

富

富

所以不行。存档是一个可怕的选择(我已将数千个存档表更正回他们的家;更正了索引,并恢复了正常性能,以及返回SELECT查询一个表而不是两个)。但是如果你存档,它不是非规范化的,更糟糕​​的是,一个副本

富

迄今为止最好的选择。同样,这是简单版本,完整版本需要了解 Codd关系模型中的时间定义(而不是批评者推销的不断变化的废话),以及符合 SQL 的平台。


我强烈不同意“规范化数据库总是比非规范化数据库快得多”的说法。即使我不强调“总是”的使用,这显然是错误的。在许多情况下,对数据库进行选择性、一致的非规范化可以极大地提高性能。

[合理的反规范化]示例:日期限制数据集的复杂联接,例如复杂业务的月末摘要。如果数据是在一个月内收集的;然后永远稳定;并且经常被查询——通过物化视图、触发器或更复杂的方法进行预计算是有意义的。

显然,我们需要明确的定义。

  • 正常化


    • 消除(不仅仅是减少)重复数据的目的
      请注意,没有复杂的连接,只有普通的简单连接。
    • 方法
      正式的关系数据建模
      (不按编号逐步遍历 NF)。
  • Un-Normalised
    未能正确规范化,在数据库中留下更新异常,并使事务混乱

  • 去规范化
    在正式规范化之后,出于性能原因,一个或多个列额外放置在选定的表中。

    • 例如。假装的“SQL”不能以可接受的速度执行正常查询。
    • 请注意,在替换不良数据库的 40 年期间,所有被声明为去规范化的数据库实际上都已被取消规范化。
  • 汇总表/物化视图
    如上文答案中详述(请再次阅读),以及仅当前图表所示,它是一个附加表,用于为历史提供汇总值,不会更改。这很常见。  

    • 没有重复的列,不能归类为“反规范化”。

您并不反对我的陈述 [规范化数据库总是比非规范化数据库快得多],这与规范化与非规范化有关,您将汇总表错误地分类为“去规范化”。


如果您可以提前知道数据库将接收哪些复杂、耗时的查询,则可以预先计算这些查询的结果——例如,将 14 表连接替换为已包含所需数据的表。

那又不一样了。造成这种情况的两个常见原因是:

  1. 数据库一开始就没有规范化,所以对大表的查询很慢
    • 正确的治疗方法是正确标准化
  2. 你假装的“SQL”太慢了
    • 解决这个问题的正确方法是获得一个真正的 SQL 平台。

这样您就需要构建一个附加文件(或物化视图)来为慢速查询提供服务。是的,那是去规范化,而且是大规模的,但更糟糕的是,它是这些字段的 100%副本

于 2010-11-30T02:52:15.273 回答
12

1)这是一个档案。其中的所有内容都不应更新。我会听从前辈的建议,让那张发票表是独立的。也许对包含标记语言的发票本身使用 blob?

2)报告服务,一个触发更新的仓库表,你可以通过脚本构建的东西......我认为这些都很好。归一化确实是理想的,但并不总是很快。我有一个我管理的大小适中的医疗保健数据库,它是完全规范化的……然后有一系列非规范化的表格,其中包含汇总的方程式和通常提取的字段。几乎所有内容都来自该非规范化集合 - 在加载文件时使用触发器附加到这些集合比每次我想查看 100,000 条记录报告时都必须从各种表中提取要快。

于 2010-11-29T05:39:37.407 回答
8

您提出了有效点,但是您对标准化及其含义并不完全清楚,例如

1) 声称保留发票原样使数据非规范化的说法是完全错误的。让我们以价格为例 - 如果您有一个业务要求,即您必须保留价格历史记录,那么只保留当前价格是错误的,它违反了要求。而且它与规范化无关,它根本没有设计好。非规范化是关于在您的模型(和其他工件)中引入歧义的可能性 - 在这种情况下,您根本没有正确地建模您的问题空间。
对数据库进行建模以支持时态数据(或版本控制和/或将数据库区域划分为存档/时态和工作集)并没有错。

看规范化而不看语义(就需求而言)是不可能的。

另外,如果您的高级开发人员看不出区别,那么我猜他没有在 RDBMS 开发方面获得资历;)

2)第二部分确实是非规范化。但是,如果您遇到过认真宣扬规范化的高级 DB 分析师,您会听到他/她说,只要您有意识地进行非规范化并确保有利于超重缺陷并且异常不会咬您,就可以完全接受非规范化。他们还将告诉您规范化逻辑模型,并且在物理模型中,您可以出于各种目的(性能、维护等)偏离理想值。在我的书中,规范化的主要目的是让你没有隐藏的异常(例如,参见这篇关于5NF的文章)

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

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

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

编辑: (2)中的“非规范化”一词是不正确的,如果您查看范式的正式定义,并且如果您认为如果设计破坏了任何范式,则该设计是非规范化的(对某些人来说,这是显而易见的并且没有其他方式)。

不过,您可能希望习惯这样的想法,即很多人而不是不必要的无用文本将使用术语规范化来尝试减少数据库中的冗余(例如,您会发现科学论文,通过我并不是说它们一定是正确的,只是作为一个警告,即调用派生属性是一种非规范化的形式是很常见的,请参见此处)。

如果你想参考一些更连贯和公认的权威(同样,并非所有人都认可),也许CJDate的话可以做出明确的区分:

许多设计理论都与减少冗余有关。归一化减少了relvars内的冗余,正交性减少了relvars之间的冗余。

深度引用自数据库:从业者的关系理论

在下一页

正如未能始终规范化意味着冗余并可能导致某些异常一样,未能坚持正交性也是如此。

因此,跨 relvar 的冗余的正确术语是正交性(基本上所有范式都谈论单个 relvar,因此如果您严格查看规范化,它永远不会因为两个不同 relvar 之间的依赖关系而提出任何改进)。

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

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

  • 确保用户(和应用程序)不能以与其谓词不一致的方式直接更新小计表(换句话说,在小计过程中存在错误)
  • 确保用户(和应用程序)在不更新小计的情况下无法更新它们所依赖的表(换句话说,某些应用程序不会在不更新总计的情况下从明细表中删除一行)

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

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

显然,您可能会说,但是,如果您正在构建的系统是为审计公司而构建的,那么您可能没有功能依赖性——他们可能正在审计正在使用手动计算或软件有问题或必须有能力记录不完整数据的人并且最初的计算可能是错误的,作为审计公司,您必须记录发生的事实。

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

于 2010-11-29T10:31:21.930 回答
5

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

于 2010-11-29T05:48:04.383 回答
5

1) 不需要非规范化。您只需要确定您需要的每个更改的详细程度,并使用适当的密钥将其持久化。

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

于 2010-11-29T10:32:33.430 回答
5

您的高级开发人员提出了非常有效的观点。我通过维护不会对历史数据进行非规范化的系统来亲自学习这些东西。

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

就数据库中的总数而言。当我对应用程序进行更改导致数字不以相同的方式相加(没有您想象的那么难)时,这已经节省了我的时间。在实时应用程序中,总数给了我一个明确的位置来纠正差异。我以前写过这个,你可以在这里阅读:http: //jlrand.com/ ?p=95

于 2011-02-01T18:13:14.533 回答
1

对于 #1

发票应根据销售额和付款计算。如果您没有详细的销售数据,包括价格/产品/折扣/运费/等,请从那里开始。

对于 #2

从头开始将会计系统写入数据库是一个大项目。确保会计师为您提供业务规则,以便您可以衡量系统的准确性。您最不想看到的是首席财务官进入 DBA 会议并宣布 DB 向客户收取过高的费用,更糟糕的是,您收取的费用过低并导致公司倒闭。

如果您有 SQL Server,请查看 Adventure Works db。如果你讨厌 MS,那么看看 Adventure Works,不要那样做。

于 2010-11-30T00:02:33.563 回答
0

数据库规范化删除了重复项并使用于数据更新的 sql 查询更有效(并提供了一些其他改进)。

但是如果您的大部分查询都用于数据选择并且选择查询同时连接到多个表,您可能会考虑对这些表进行非规范化。它将增加数据所需的磁盘空间量、执行 sql 更新查询的时间,但会改进选择查询。

于 2010-11-29T05:52:12.360 回答
0

似乎您正在考虑是否应该创建数据仓库。您永远不应该出于历史报告的目的对数据库进行非规范化。创建存档并将您的信息存储到数据仓库中将同时做到:对大部分信息进行非规范化并维护您的数据历史记录。

于 2016-08-05T23:48:00.643 回答