数据库设计之“有时不得不违背的第三范式”

数据库设计之“有时不得不违背的第三范式”

在博客园上看到了一篇关于数据库范式的文章《数据库设计中的五个范式》: 
 第三范式规则查找以消除没有直接依赖于第一范式和第二范式形成的表的主键的属性。我们为没有与表的主键关联的所有信息建立了一张新表。每张新表保存了来自源表的信息和它们所依赖的主键。   
 关于第三范式的思想,我想有很多朋友都熟悉,在数据库设计时,也是我们尽可能采用的范式之一,第三范式的出发点是什么?就是尽可能的减小“数据冗余”、并也能得到“数据”的整洁性,提高维护性,不容怀疑,第三范式是我们努力、必须要去遵从的。 
 然而,有很多朋友把第三范式作为“不死的法宝”,但其实在实际的应用中,我们还是要从不同的业务出发,要合理的应用“第三范式”。下面我也就简单的举个例子: 
    一张订单会关联很多的基础资料,如:客户,付款条款,货运方式等,这些信息是有专门表进行维护的,在下订单时也是用下拉框选择的,在保存订单信息时,按照“第三范式”的要求,那应该只保存对应的主键值就OK了。因为这样可以避免数据冗余,但对我来说,我不会这样做,我会把客户的名称、联系电话、付款条款名称等订单上要求记录的信息直接COPY到订单的表中。 
   这样看来,我们违背了“第三范式”,是的,但在这里,我们违背“第三范式”也是有理由的: 
   1我不想在订单下达完以后,删除了某条付款条款,导致这些订单无法知道“真实的付款条款”了,这肯定不合理。 
   2我也不想,因为下了这张订单了,而“严格控制”付款条款的“删除”功能,这也不合理,凭啥不能删除了?下个月这个“条款”确实永远不会采用了。 
   3我也不想,付款条款修改后,导致以前所有采用此付款条款的订单都变成新的条款,那在系统中的订单如何与手头的纸张订单再对应,这肯定也不合理。 

  所以,我的设计原则是,对于这种订单我们应该采用“隔离”的方式来对待,让基础数据COPY到订单中,这当然会违背所谓的“第三范式”,但这也是实际的需要啊。理论与实际是有差别的。 
  订单--这种在现实中以实物的方式存在,实物具有与基础数据的参考性,而不是关联性,基础数据只能是作为订单这个实物的一个“参考”,而不是“关联”,这可以称为“独立性”;再者,订单具有一定的历史性,因为是实物,在实际的过程中,是即时生成的,那么在生成的当时去参考了基础数据,订单就在当时被确定,确对不能因为基础数据的修改而导致订单被“无辜变性”了,这也就是订单的“历史性”,当以后翻阅这些纸张订单时也能对应上系统里的订单。 

   这是我所理解的最典型的例子,在实际的系统设计中,我们应该多思考一下,是不是要采用“第三范式”,不要再盲目追捧了。


  以上纯属我个人意见,仅供参考,欢迎大家讨论。

Feedback

#1楼    回复  引用  查看    

2005-05-08 14:23 by Lostinet     

例如今天卖了2个面包,价格是3块,那么Sales表里应该把当时的价格也纪录了。而不只是纪录了这个面包的型号,因为面包的价格是会变化的。 
关键是要对数据的含义与关系有清晰的认识。而这种纪录本身不属于冗余,并不和数据库设计的理论有冲突的地方。 

就客户改名字的说法,订单纪录的数据可以当作是“订单时的客户名称”,与客户表的“当前名称”有着不同的意义,这就不是冗余。 


#2楼    回复  引用  查看    

2005-05-08 14:25 by 嘿嘿     

范式原则是用于指导性地设计数据的存储结构的,仅仅只是理论。必要的时候是需要反范式,或者容忍一定的数据冗余,使得系统的性能或逻辑清晰性上更好。 

范式是一种基于纯粹的从数据存储量上来优化数据结构的方式,并不是一个非常通用的东西。 

另外: 
“1我不想在订单下达完以后,删除了某条付款条款,导致这些订单无法知道“真实的付款条款”了,这肯定不合理。 
 2 我也不想,因为下了这张订单了,而“严格控制”付款条款的“删除”功能,这也不合理,凭啥不能删除了?下个月这个“条款”确实永远不会采用了。” 

上述情况,实际上可以考虑采用仓库性质的数据管理方法:即,对于所有的数据,分为可用与不可用,放在不同的表间,然后可以把其中的数据挪来挪去。 

你说的情况非常现实,从业务角度出发,将数据结构优化为最高存储效率的是没有意义的,范式更多是一种指导,而不是原则。

#3楼    回复  引用  查看    

2005-05-08 14:39 by 寒枫天伤     

我觉得这是理论与实践中的区别,或者说,一般人对范式的理解并不对。 

一般而言,范式是作为指导性原则存在的,范式提供了一种指导性的依据,可以实现最终冗余数据的消除。但在实际上,冗余数据并不是违法的,也不是不允许的,除了性能上的需要外,很多情况下都需要冗余数据来实现参照完整性。 

数据库设计最主要的概念之一就是参照完整性,一个完整的数据库中,存储的关联信息,应该是要么都存在,要么都消失,如楼主说的: 
“我不想在订单下达完以后,删除了某条付款条款,导致这些订单无法知道“真实的付款条款”了,这肯定不合理。” 
理论上,如果依照范式实施的参照完整性,要求这里的付款条款所关联的订单全部都删除,否则的话,该付款条款是不应该删除的。 

不过,更重要的是:楼主所说的“付款条款”与“订单”间应该是弱耦合关系,它们应该是分开的。这个矛盾产生两张有关联的表的存储方式上。这个情况而言,付款条款应该是作为辅助表而存在的,有可能这个表仅仅是为了提供下拉列表或其它什么方式的便利性输入接口,那么“付款条款”本质上只是存储了的数据,它与“订单”间不应该有关系。 

楼主的做法从这个角度上而言,并未违反第三范式。第三范式应该集中应用于有业务关系的表,而不应将辅助表也包括在内。

#4楼    回复  引用  查看    

2005-05-08 14:56 by hudan     

基本同意楼主的观点,但就楼主提出的例子谈一下我的想法: 
就你说的这种,我的做法是在"付款条款"基本表中增加一列是否删除的标识列,删除的时候修改"删除标识"列,以后用户下订单的时候就不会看到这个条款了,但是查询历史数据的时候仍然可以查询到以前的条款. 
对于你说的第三点"系统中的订单如何与手头的纸张订单再对应",我的这种做法就无法实现对应了.就会全部显示修改后的条款.

有些情况下为了提高查询速度也冗余几个字段,我认为是值得的. 






#5楼    回复  引用  查看    

2005-05-08 15:07 by mikespook     

《迦勒比海盗》里面有一句话:“法典,更像是指南,而不是准则……”

#6楼 [楼主]   回复  引用  查看    

2005-05-08 15:21 by 听棠.NET     

@寒枫天伤 : 
我觉得这也是指关联,因为我们也可以采用第三范式来实现,只是订单在这种情况下会遇到好多的问题. 

在订单的设计中,我一般会有“条款ID”、“条款名称”,也就是我会把主键对应上以后,把Name也带过来,之所以要带入"条款ID",主要还是因为订单需要修改,在修改时,可以默认选中原来的值。而“条款名称”的带入就是我前面的说的原因。 
因此,从这一点来说,这些确实是违背了“第三范式”。

#7楼    回复  引用  查看    

2005-05-08 18:17 by lay     

呵呵,我觉得楼主举的例并不是违反了,字典表相对来说要特殊些。最终还是得根据具体业务来定

#8楼    回复  引用  查看    

2005-05-08 18:58 by 上山砍柴去     

“1我不想在订单下达完以后,删除了某条付款条款,导致这些订单无法知道“真实的付款条款”了,这肯定不合理。 
==为何要允许删除付款条件?不可以设为失效吗? 

2我也不想,因为下了这张订单了,而“严格控制”付款条款的“删除”功能,这也不合理,凭啥不能删除了?下个月这个“条款”确实永远不会采用了。 
==不能删除就是不能删除,规定下来不可以吗?同样,设为失效不行吗? 

3我也不想,付款条款修改后,导致以前所有采用此付款条款的订单都变成新的条款,那在系统中的订单如何与手头的纸张订单再对应,这肯定也不合理。 
==付款条件可以随意修改的吗?同样,需要修改的不可以新建一个条件吗? 

我认为,你的数据库这样设计是非常不成功的。就因为你上面的理由就造成成千上万条记录的冗余?这合理吗? 

一已之见,只承担有限责任。。。 

#9楼    回复  引用  查看    

2005-05-08 19:26 by      

其实楼主说的很有理,好多时候,我们要因事而宜不能把住死理不放,但是一个理论的存在是有一定的道理的,起码在一段时间内,所以我想去应用第三式,还是必要的

#10楼    回复  引用  查看    

2005-05-08 20:12 by 红移     

可以这样设计 

订单表: 
订单号(主键) 
条款号(外键) 
[其它内容] 

条款表: 
条款号(主键) 
有效标记(bit) 
条款内容 
[其它内容] 

列订单时只列出条款表中有效标记为1的条款。删除条款的前提是该条款不再有人用到(在订单表中)。如果有人用到,则只是把有效标记设为0。当删除订单时,检查条款表中相应的条款,如果它已经无效并且除了本订单外无其它人用到,则顺便删除该条款。 

#11楼    回复  引用  查看    

2005-05-08 20:29 by 一川烟草     

我搞8年的业务系统,从单机程序到800多个表的大系统。 
我的感觉是冗余确实是非常必要的。 
用空间换取效率。 
我有一个check表,餐饮系统的。 
包括了开单员 
开单销售点 
开单餐段 

结单员 
结单销售点 
结单餐段 


#12楼    回复  引用  查看    

2005-05-08 20:36 by 一川烟草     

我的感觉是冗余确实是非常必要的。 
用空间换取效率。 
我有一个check表,餐饮系统的。 
包括了开单员 
开单销售点 
开单餐段 
开单终端 
结单员 
结单销售点 
结单餐段 
结单终端 
这些都是外键,而且每个外键的描述都包括,中文,英文,其它语言三个描述。 
在给客户显示数据的时候,得根据当前语言显示对应描述。这样的查N个表返回数据,一个稍有规模的餐厅,每天的单数有上千张。这样的查询效率极地下。 
经过冗余之后,以上8个字段我们把它冗余为32个。把全部描述都丢到check表,查询效率成倍提升。并且简化了程序员的开发。 
对于设置表的删除,我的看法是为什么要真的删除呢?设置表的主键完全可以设计成用户不可见的。用户仍然可以删除,不过真实的数据只不过作了个删除标记而已。 


#13楼 [楼主]   回复  引用  查看    

2005-05-08 21:27 by 听棠.NET     

@上山砍柴去 : 
其实我非常不想跟你讨论这些问题,你说的几个跟我的意思根本不是一回事。你可以用“是否有效”来实现,你的意思是所有的基础都有“是否有效”,可客户就是不喜欢这种“是否有效”的标志,"是否有效"是在客户"暂时不用"的情况下会采用,知道什么叫"暂时"吗?再者,时间一长你搞那么多"无效"的多恶心啊。 
还有我说的就是能允许客户删除,你凭什么不让客户删除,你以为这是给客户考虑吗? 
自己去悟吧。

#14楼    回复  引用  查看    

2005-05-08 21:52 by rIPPER     

客户一定要直接看到这个有效标志么,他不喜欢看,你可以不让他看到,这很容易嘛。 

要玩客户不要被客户玩,他说要删除你就真从数据库里面删掉一条记录?

#15楼    回复  引用  查看    

2005-05-08 22:10 by 一川烟草     

我道是同意作标记,对于用户来说,什么叫删除?界面上看不到了就是删除。 

#16楼 [楼主]   回复  引用  查看    

2005-05-08 22:25 by 听棠.NET     

我知道大家可以作标记,我也是对一些需要用到“标记”的我会用,但如果那些根本没必要用的,都采用“标记”的方式来处理,我觉得很不爽,明明已经不再可用的东西,在数据库里放着,太恶心了。。还招来程序的麻烦。。

#17楼    回复  引用  查看    

2005-05-08 22:40 by 小陆     

我觉得冗余带来的最大的问题在于难以同步,占用空间倒是其次。

#18楼    回复  引用  查看    

2005-05-08 23:01 by 楚潇     

经过实际的项目的话,就明白楼主所说的道理了。 
做一个删除标志也是可行的,但是很多的时候,没有楼主说的方法好。 


#19楼    回复  引用  查看    

2005-05-09 00:12 by yfmine     

To:hudan 
个人意见,在数据库里只添加新规则,不能改变已有规则,任何改动都视作新添加,这样就能实现第三点了。不过这样的数据也。。。

#20楼    回复  引用  查看    

2005-05-09 00:57 by wljcan     

同意 寒枫天伤 的观点。 


从文中的描述来看,应该是弱耦合,但是这段话又有些问题: 

*****在订单的设计中,我一般会有“条款ID”、“条款名称”,也就是我会把主键对应上以后,把Name也带过来,之所以要带入"条款ID",主要还是因为订单需要修改,在修改时,可以默认选中原来的值。而“条款名称”的带入就是我前面的说的原因。 

既然已经 将Name copy过来了,为什么还要ID呢? “默认选中原来的值” 好像不成立。 

另外,技术讨论没有必要有这么浓的火药味。 :=) 

#21楼    回复  引用  查看    

2005-05-09 08:56 by 老翅寒暑     

关于文中的订单中客户信息部分一定是需要copy到订单表中的。这么多人考虑问题,怎么没有人从法律效力方面来考虑呢?做一个系统首要要保证的是呈现的统一性,一个订单录入之后,只要没有修改,无论多长时间,它的输出格式和内容都不能有变化。这个是法律意义上的完整性。总不能今天订单上是xx公司,明天xx公司改名成了xxx有限公司,难道之前和xx公司的订单(或者合同)就要自动变成xxx有限公司?

#22楼 [楼主]   回复  引用  查看    

2005-05-09 09:15 by 听棠.NET     

@老翅寒暑 : 
我说的就是你指的意思,一张订单已经确定,那么具有独立性与历史性。 

@ 上山砍柴去 : 
客户说是保留,那你当然保留好了,我没有否认这一点啊,但就怕客户说不要保留,你却偏偏要保留啊。至于数据冗余你看下面的。 

至于“为什么还要ID呢?”就是有可能会进行修改,那么如果不带ID过去,修改时如何选中默认值,要是不能默认选中,结果客户修改只是为了修改其它的一个属性,而这个ID可能会在不注意的情况下被修改掉了。因为是下拉框的。 
至于作标记,也是一种方式,比如客户说给作个标记或者是此值是应该具有“暂时不用”的情况,那么用标识当然是最好的。 
就像老翅寒暑 说的,一张订单是实物,在业务实际中明明确确存在,要是从法律上讲也具有法律,就算没有法律性,我们也要把它视为一个独立性,有朋友说,以后修改怎么办?一张订单在当时下达,就具有当时的即时性与历史性,不可能因为你以后的基础数据的修改而导致订单失去原来的属性,因此这些所谓的“冗余”只是从"第三范式"上讲的,而在实际的业务中,其实没有所谓的“冗余”。 


#23楼    回复  引用  查看    

2005-05-09 09:39 by na     

如果你要隔离,那还有很多要隔离哦.... 
为何不在删除条款时看看是不是已经用过这个条款呢? 
基础数据如果使用过是不给删除的。

#24楼    回复  引用  查看    

2005-05-09 09:41 by 上山砍柴去     

同意楼上的。 
================== 
我并没有受伤,比你刻薄的人多的是。 
有人附和是因为他们比你更没有经验,至少站在管理的角度来讲,你这样的观点更是不恰当的。 
作为基础数据,修改和删除的权限都是比较高的,特别是这种有关联其它数据的基础数据,使用后更是不能随意修改和删除的,这在系统一级就应该予以禁止。系统级不能解决的,就要在管理一级予以解决。 
如果依你这样的设计,那么其它的基础数据不都要带进别的表中去?产品数据,供货商数据,客户数据,。。。。等等等等。你这样的数据设计有你认为是成功的吗? 
你认为没法跟我说,至少你觉得说服我有难度,我认为你这样的观念去做需求分析肯定是失败的。最终出来的产品也肯定是个N不像产品。懂系统设计没什么也不起,随便找几个人来都会做。反正都是客户说了算。出了错也是客户自已找的。

#25楼    回复  引用  查看    

2005-05-09 09:56 by 小陆     

根据文中的描述, 订单记录以后, 需要保留当时即时性和历史性, 所以, 保留这些数据不能叫做冗余, 因为这些信息是必要.所以从原则上说不违反第三范式. 

关键在于保留的方式. 既然将name已经复制过来了, id我觉得就是冗余了, 并且这两个数据可能是不一致的, 如果以后需要修改的话, 直接显示name就是了. 如果要便于用户输入, 可以使用输入+选择的方式. 
另一种做法就是保留每一个配置的版本, 每当用户修改或者删除一个配置数据, 不是实际的进行修改, 而是添加新的version,这样的方式是最好的, 完全符合第三范式. 

至于客户要求删除, 我理解是: 客户看不见的就可以认为删除了, 没必要真的从物理上删除掉. 

没有什么原则是"不得不违背"的, 干出违背原则的事情一定要有更加有力的原则作为后盾. 比如: 适当冗余可以提高运行效率,合理冗余可以增加系统容错性. 如果单纯为了简化开发的过程, 我的体会是: 没有冗余开发最简单.

#26楼 [楼主]   回复  引用  查看    

2005-05-09 10:20 by 听棠.NET     

@小陆 : 
ID可以考虑不保留,确实可以直接修改NAME,因为从某种意义上说,参考的数据COPY过来后,就认为已经确定了。 

至于删除的控制,有些基础数据是需要使用“控制来限制的”,但不是全部。为何有人不思考实际中的一些差别呢。 
na说有很多要隔离??也不是啊,具有独立性与历史性的实物,是建议要隔离的。 

上山砍柴去说这么多人都是没有经验的?? 
我真不知道,是不是很有经验的人跟你一样,所有的基础数据都采用“删除控制”来做的,你可以去调查一下。

#27楼    回复  引用  查看    

2005-05-09 10:37 by [email protected]     

对于主表的删除当然是用标记来做最好. 
如果以后客户又需要用已经删除的数据怎么办?

#28楼 [楼主]   回复  引用  查看    

2005-05-09 10:41 by 听棠.NET     

@[email protected]
对,如果那些需要“暂时”不用的,是应该要用标记来做的。应该可以修改状态,也可以删除吧,要是他们确实想删除。 
其实这种删除不删除无所谓了,问题是基础数据应该COPY过去。

#29楼    回复  引用  查看    

2005-05-09 10:59 by Laser.NET     

我个人比较赞同Lostinet的看法,其实不能算是违反第三范式。 

你在 “订单表” 中存储的是 “那个订单当时的客户信息” 和 “那个订单当时的付款条款” 这些信息离开了那个订单就没有意义,所以它们完全依赖于订单主键,而那个订单也需要这些信息,在你所讲的业务环境下它们对订单来讲必不可少,所以这本来就符合第三范式:) 


#30楼    回复  引用  查看    

2005-05-09 11:02 by Austin leng     

有道理哟,想问一下听棠.NET,一个表里的字段数如果太多,你说有没有问题,比如有张表TestTable,里面包含了60多个字段,你认为这有不有问题?

#31楼    回复  引用  查看    

2005-05-09 11:05 by Austin leng     

数据库设计,唉,很重要,哪位牛人推荐一本牛书,谢谢啦

#32楼 [楼主]   回复  引用  查看    

2005-05-09 11:15 by 听棠.NET     

其实从业务角度讲,这种数据的COPY不能算是违反第三范式,那看一张主表可能会有相当多的关联,那么也是要根据这张主表的信息情况,比如订单,那些订单本身所固有的属性应该从基础表COPY过来,而对于“订单类型”可能不属于订单本身客观存在的属性,那么我们可以采用外键关联的方式。象这种那么在删除上是需要作限制的。 

#33楼    回复  引用  查看    

2005-05-09 14:50 by Arming     

大家讨论这么多,我看基本上是两种观点:1是Copy。2是外键引用,但通过“删除标志”控制。 
第一种表面上看是不违反第三法则,因为它可虑到条款的变更。寒枫天伤和 听棠.NET都说了相应理由。 
这里的关键也就在于“条款”,老翅寒暑也明确提出了条款的业务含义。那么条款信息也不是简单的数据字典业务所能涵盖。条款的信息维护和订单引用条款的地方就必须有些特殊的代码来体现这个特殊性。 
通过直接Copy来保证订单的“法律性”和新订单使用最新条款,确实能节省代码量,但数据冗余也是毋庸置疑的,试想,一个2000字左右的协议,每个订单都拥有一份,而我这条款实际上也就两三年一变。 
如果对条款信息引入一个版本概念,或者是否启用概念(上山砍柴的一个观点,不过他的口气确实不好,不是讨论问题的态度),实际上确实也应该存在这么一个概念,(注意,不是所有的引用信息都是如此维护。比如性别)。条款一旦被某订单引用,就不允许修改或删除,只能以版本变更方式变更。 
对无效条款的删除(太多了确实不爽),可以在条款维护业务里控制,比如显示某条款当前有多少订单引用了,不过这只是易用性问题。可以通过增强界面控制实现。 
至于新订单时能使用什么条款,这也是具体业务决定,我觉得界面上如何显示,下拉框,还是弹出窗口,都不应该在此问题内讨论。通常会在一个条款集合类提供一个静态方法,提取合适的条款集合作为新订单的选择。 
订单修改时,是否能重新选择条款,或者此时条款刚刚更新,是否自动启用新条款,这些都由具体业务来决定,也不是此问题范畴。 

BW:我在设计或开发时也不喜欢被条条框框约束。一切根据具体情况而定,假如项目小,或时间紧,资源不足,再或者这个条款长度也不长,我会毫不犹豫地使用Copy。 
但假如条款确实长到让我感觉冗余比较大,以及后期的可维护性。确实应该花时间考虑如何设计条款信息维护的业务。 


#34楼    回复  引用  查看    

2005-05-09 15:30 by step     

我碰到的应用也用到了冗余,当时觉得不符合范式要求,总是不情愿的认同,今天终于心服了。总是被理论束缚的心态很不舒服啊!

#35楼 [楼主]   回复  引用  查看    

2005-05-09 15:38 by 听棠.NET     

@Arming : 
你的观点跟我们大致一样,只是你可能没搞清楚什么叫“付款条款”,英文名叫“Payment Term”,它不是一个2000字的协议,只是说明30天付款还是60天付款。 
给你看个图:

 

#36楼    回复  引用  查看    

2005-05-09 16:46 by rIPPER     

payment term 的 term在这里翻译作条款,还要搞清楚? 

term 
n. 

1. 
A. A limited period of time. 
B. A period of time that is assigned to a person to serve: a six-year term as senator. See Synonyms at period. 
C. A period when a school or court is in session. 

2. 
以下略 ... 

你们那儿做l10n的太烂了,赶快建议老板辞掉他 :)

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。