数据库规范化 - 谁是对的?

Arm*_*man 14 sql database redundancy normalization

我的教授(声称多年来对系统开发有了深刻的理解),我正在争论数据库的设计.

举个例子:我的教授坚持认为这个设计是对的:(列表)

Subject_ID
Description
Units_Lec
Units_Lab
Total_Units
Run Code Online (Sandbox Code Playgroud)

等等...

注意总单位列.他说必须包括这个专栏.我试图解释这是不必要的,因为如果你想要它,那么只需添加两个就可以进行查询.

我向他展示了我在一本书中找到的一个例子,但他坚持认为我不必过多地依赖书籍制作我们的系统.同样的情况适用于此类似的案例:

student_ID
prelim_grade
midterm_grade
prefinal_grade
average
Run Code Online (Sandbox Code Playgroud)

等...

他希望我把平均值包括在内!无论我走到哪里,我都会发现自己在阅读那些让我相信这违反了规范化的文章.如果我需要平均值,我可以轻松计算三个等级.他列举了一些场景,包括('嘿!如果查询被意外删除怎么办?你会做什么?这就是为什么你需要把它包含在你的桌子里!')

我是否需要重建我的数据库(包含大约40多个表)才能符合他的要求?我错了,只是忽略了这些事情?

编辑:

另一件事是他想在支付表中包括总金额,我认为这是不必要的(只需计算产品的单价和数量).他指出,我们需要该列来计算对整个系统管理至关重要的借方和/或贷方,这是平衡交易所需要的.请告诉我你的想法.

Qua*_*noi 13

当你说你的解决方案更加规范化时,你是对的.

然而,有一种称为非规范化(google for it)的东西,它故意违反规范化规则以提高查询性能.

例如,您想要通过减少数量或总单位来检索前五个主题(无论是什么).

您的解决方案需要对两个表(subject和unit)进行完全扫描,连接结果集并对输出进行排序.

你的教授的解决方案只需要从索引中获取前五个记录total_units.

这当然是以增加维护成本(在计算资源和开发方面)为代价的.

我无法告诉你谁在这里"正确":我们对项目本身,数据量,要进行的查询等一无所知.这是一个需要为每个项目做出的决定(对于某些项目,它可能是核心决定).

问题是,教授确实有这个要求的理由,这可能是也可能不是.

为什么他自己没有向你解释上面的一切,这是另一个问题.

  • 很好的答案.如果Normalization就是一切,那么所有数据库都将处于第5范式,并且如果不编写带有多个连接的巨大sql查询,您几乎无法找出程序发生的问题.我曾经研究过度规范化的系统,而且它是一个真正的PITA.正常化和易用性之间存在良好的中间立场. (2认同)

ari*_*eet 12

你是绝对正确的!规范化的一个规则是减少那些可以通过使用其他属性的值容易推导出的属性.即,通过执行一些数学计算.在您的情况下,只需添加即可获得总单位列.

告诉你的教授,拥有那个特定的列将显示传递依赖的明显迹象,并根据第3个规范化规则,建议减少这些.

  • 在这种情况下唯一可能的例外 - 我正在努力*尝试*给这位教授*一些怀疑的好处 - 是否,根据系统中的业务规则,Total_Units并不总是必须等于其他两列的总和...例如,如果Total_Units可以包括由不了解数据库规范化的教授自行决定授予的奖励单位.:-) (3认同)
  • @Arman你也可以告诉你的教授,像我这样的人(管理电子商务团队的人)永远不会雇用像他建议的那样编写数据库表的人.那会让你在第一轮面试中被淘汰出局. (2认同)

usr*_*usr 6

除了redskins80的最佳答案之外,我想指出为什么这是一个坏主意:每次需要更新其中一个源列时,您还需要更新计算列.这是更容易包含错误的工作(也许1年后,当不同的程序员改变系统时).

也许你可以使用计算列代替?那将是一个可行的中间立场.

编辑:非规范化有它的位置,但它是最后一个措施.这就像化疗一样:医生只会给你注射毒药,以对你的健康造成更大的威胁.这是最后一步.


小智 6

认为添加此内容非常重要,因为当您看到问题时,我认为答案并不完整.最初的问题得到了很好的回答,但这里有一个小问题.所以我只考虑下面引用的补充问题:

另一件事是他想在支付表中包括总金额,我认为这是不必要的(只需计算产品的单价和数量).他指出,我们需要该列来计算对整个系统管理至关重要的借方和/或贷方,这是平衡交易所需要的.请告诉我你的想法.

这个编辑很有趣.基于事实,这是一个处理金钱的交易系统,它必须是负责任的.我采取一些基本术语:交易,产品,价格,金额.

从这个意义上来说,非常规或甚至需要非规范化.为什么?因为你需要它负责.因此,当事务被注册时,它可能永远不会被修改.如果您需要更正它,那么您进行另一次交易.

现在是的,您可以计算出例如产品价格*金额*税金等.这在标准化意义上是有意义的.但是,您需要完全锁定所有相关记录.因此,例如产品表:如果您在交易之前更改价格,则应在交易发生时将其考虑在内.但如果之后价格发生变化,则不会影响交易.

因此,加入transaction.product_id = products.id是不可接受的,因为该产品可能会发生变化.例:

2012-01-01 price = 10
2012-01-05 price = 20
Transaction happens here, we sell 10 items so 10 * 20 = 200
2012-01-06 price = 22
Run Code Online (Sandbox Code Playgroud)

现在我们在2012-01-10查找交易,所以我们这样做:

SELECT 
    transactions.amount * products.price AS totalAmount 
FROM transactions 
INNER JOIN products on products.id=transactions.product_id
Run Code Online (Sandbox Code Playgroud)

这将给出10*22 = 220,所以这是不正确的.

所以你有两个选择:

  1. 不允许在products表上进行更新.因此,您对该表进行了版本化,因此对于每条记录,您都添加了一个新的INSERT而不是更新.因此,交易始终指向正确的产品版本.

  2. 或者您只需将字段添加到事务表中.因此,将totalAmount添加到事务表并在插入事务时计算它(在数据库事务中)并保存它.

是的,它是非规范化的,但它有充分的理由,它使它负责.您只需要知道并且已通过交易,锁等进行验证,交易发生的时刻与所描述的产品相关,价格= 20等.

接下来,无论如何,当你必须这样做时,这只是非规范化的一件好事,它很容易运行报告.一个月,一年等的总交易金额.这一切都很容易计算.

规范化有好处,例如没有双重存储,单点编辑等.但在这种情况下,您只是不想要这个概念,因为这是不允许的,而不是首选的事务日志数据库.

将交易视为现实世界中发生的事情的注册.它发生了,你写下来了.现在你无法改变历史,它是按原样写的.它发生了,未来不会改变它.