woaidongmao

文章均收录自他人博客,但不喜标题前加-[转贴],因其丑陋,见谅!~
随笔 - 1469, 文章 - 0, 评论 - 661, 引用 - 0
数据加载中……

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

在博客园上看到了一篇关于数据库范式的文章《数据库设计中的五个范式》:
 
第三范式规则查找以消除没有直接依赖于第一范式和第二范式形成的表的主键的属性。我们为没有与表的主键关联的所有信息建立了一张新表。每张新表保存了来自源表的信息和它们所依赖的主键。  
 
关于第三范式的思想,我想有很多朋友都熟悉,在数据库设计时,也是我们尽可能采用的范式之一,第三范式的出发点是什么?就是尽可能的减小数据冗余、并也能得到数据的整洁性,提高维护性,不容怀疑,第三范式是我们努力、必须要去遵从的。
 
然而,有很多朋友把第三范式作为不死的法宝,但其实在实际的应用中,我们还是要从不同的业务出发,要合理的应用第三范式。下面我也就简单的举个例子:
   
一张订单会关联很多的基础资料,如:客户,付款条款,货运方式等,这些信息是有专门表进行维护的,在下订单时也是用下拉框选择的,在保存订单信息时,按照第三范式的要求,那应该只保存对应的主键值就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 ayya__@hotmail.com     

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

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

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

@ayya__@hotmail.com :
对,如果那些需要暂时不用的,是应该要用标记来做的。应该可以修改状态,也可以删除吧,要是他们确实想删除。
其实这种删除不删除无所谓了,问题是基础数据应该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     

大家讨论这么多,我看基本上是两种观点:1Copy2是外键引用,但通过删除标志控制。
第一种表面上看是不违反第三法则,因为它可虑到条款的变更。寒枫天伤和 听棠.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天付款。
给你看个图:

 clip_image001

#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的太烂了,赶快建议老板辞掉他 :)

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

2005-05-09 17:08 by 听棠.NET     

clip_image002

老兄,在SAP里全都翻译成付款条款的!!

你查了查字典就以为可以想翻什么就翻什么吗?

不了解ERP也不至于要把老板炒了吧???


早知道我就把文章写成Payment Term了,那估计你肯定知道是什么意思了。哦,你不信的话用Google搜一下吧。

#38    回复  引用  查看    

2005-05-09 18:35 by james wong     

Shit, 各人应用的环境不一样,各人的经验也不一样。干嘛争来争去的?哪个你用好了没问题就是对的,否则就是错的。别人是对的你拿过来不一定是对的,别人是错的也不一定在你那里就也是错的。既然别人是对的你也不一定是对的,别人是错的你也不一定是错的,那这个问题也就是不对不错的。那么,那么还要讨论谁对谁错做什么呢??

打雷了。。下雨了。。。大家快收衣服啊。。。

#39    回复  引用  查看    

2005-05-09 19:17 by Arming     

SAP里全都翻译成付款条款” ,这样看起来应该是没有什么讨论必要了。

#40    回复  引用  查看    

2005-05-09 19:39 by rIPPER     

原来是sapl10n做得烂 ;) 不知道包给哪个公司做的,嘿嘿

#41    回复  引用  查看    

2005-05-09 20:49 by 知道得越多知道的越少     

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

 

 字段多不是问题,你可以去看看SPS的数据表Userdata,微软为每一种数据类型预留了64个字段,该表的字段数超过300,记录数在100万条以上,也没有见严重影响性能.

很多时候,数据冗余是提升性能最有效的方法,特别是三个以上的表间联接,通过数据冗余来改善性能是非常明显的,同时也减小了SQL的复杂度.

#42    回复  引用  查看    

2005-05-09 21:33 by 一川烟草      

为什么会认为ID不用保留呢?
如果要针对付款方式做统计,没有ID,难道就用名称来统计吗?
例如某付款方式曾经叫 牡丹卡(当时工行只有一种卡) 后来增加了牡丹灵通卡,牡丹储蓄卡,原来的牡丹卡名称改为牡丹信用卡,但ID不变。
那么请问赞成不保留id的朋友,怎么统计订单中付款方式为牡丹信用卡的总金额呢?

#43    回复  引用  查看    

2005-05-09 21:42 by 小陆     

冗余这样的方式一般都用于提高系统的性能和增强系统的容错性,并且一般说来,冗余会增加开发的复杂度。在冗余数据之间进行同步是需要耗费精力的,要在开发团体中形成一致的意见,并且要保持后来加入人员认识的统一。这需要严明的纪律和充分的交流。如果一个数据只有一个来源,大家的认识很容易一致。如果一个数据要保存在多个位置,则团体的每个人中对一个数据的来源,更新的时候要不要同步,选取的时候到底从哪里取等一些事项上容易产生分歧,极易造成脏数据。
冗余要有明确的目的,不能贪图一时之便,否则带来的可能是麻烦。第三范式是一个正确的准则,也许有时可以违背,但是一定要有一个更加强的准则作为支持,要考虑具体的情况。这个更强的准则,我想应该不是可以省一点事

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

2005-05-09 22:29 by 听棠.NET     

@小陆 :
现在我现在讨论根本不是指你说的冗余,我所指的这种冗余是必须,根本不需要同步不同步的。因为订单的那些信息具有历史性。从一点上来说,你说的冗余是另一回事。

#45    回复  引用  查看    

2005-05-12 11:42 by 平常     

1我不想在订单下达完以后,删除了某条付款条款,导致这些订单无法知道真实的付款条款了,这肯定不合理。
2
我也不想,因为下了这张订单了,而严格控制付款条款的删除功能,这也不合理,凭啥不能删除了?下个月这个条款确实永远不会采用了。
3
我也不想,付款条款修改后,导致以前所有采用此付款条款的订单都变成新的条款,那在系统中的订单如何与手头的纸张订单再对应,这肯定也不合理。

参照(Reference
参照在数据库设计中是一个比较复杂的问题,它是实现数据的完整性主要要素之一,详细论述参考后面数据的约束。

PowerDesigner中,可对参照完整性进行各项设置,参照的基数从0n,对修改和删除约束可分别设置为NoneRestrictCascadeSet NullSet Default。由于INSERT包含在UPDATE操作中,因此没有单独的INSERT约束。

约束的不同设置产生不同的效果,以修改为例(删除相同):
None
:父表修改,子表不影响。
Restrict
:父表修改,如果子表存在,则出错。
Cascade
:父表修改,如果子表存在,则相应的修改。
Set Null
:父表修改,如果子表存在,则相应置空。
Set Default
:父表修改,如果子表存在,则相应置默认值。

#46    回复  引用  查看    

2005-05-12 11:50 by 平常     

数据库的完整性
数据库完整性可通过存储过程、声明性参照完整性(DRI)、
数据类型、约束、规则、默认值,以及触发器来实现。在数据库内,这些功能各以特有的方式发挥作用。综合利用这些完整性功能,可以使数据库灵活,易于管理,而且很安全。
数据完整性概念分为几个方面。
表域完整性
通过主键来强制表的域完整性。
引用完整性
利用参照来加强表之间的逻辑关系。
数值域完整性
任何输入的数据在类型和范围上必须与指定的数据类型相匹配,只有当某列被说明允许NULL值,才允许向该列输入NULL
数据库的性能测试
生成数据库之后,应进行数据库性能测试,以便优化数据库的设计,因此需要生成测试数据,由于是性能测试,数据的规范性要求不高。通过PowerDesigner可方便地生成测试数据(Generate Test Data),完成性能测试。
数据的约束
O-O
约束
对父表的INSERTUPDATEDELETE操作没有限制。
M-O
约束
对父表操作的约束:
父表的INSERT操作,对M-O约束,父表中间的记录可以没有任何约束地添加到表中,因为这种约束中不一定必须有子女。
父表的键值修改操作,只有在子表中其所有的子女对应均做修改后,才能修改,即一般采用级联更新的方法。
父表的删除,父亲只有在其所有子女均被删除或重新分配之后该父亲才能被删除。
强制对可选(M-O)约束
O-M
约束
父表操作的约束:
父表的INSERT操作,对O-M约束,一个父亲只有当至少当它的一个、子女同时被加入或至少存在一个合法的子女时,才能被加入。
父表的键值修改操作,只有当一个子女被创建或已经有一名子女存在才行。
父表的删除,理论上删除父亲是没有限制的,实际上,删除主表记录时,不采用级联删除子表的方案,而采用将子表的外键置空。
可选对强制(O-M)约束

M-M
约束
父表操作的约束:
父表的INSERT操作,可能随后需要生成子女,即在子表中创建新的行。也可能通过对子表的重新分配来实施完整行限制。
父表的键值修改操作,只有在子表对应的外键的值修改成新值时才能进行。实际可能是先创建新的父表纪录,接着修改子表所有对应的纪录,使其与父表的新纪录关联,最后删除原父表纪录。
父表的删除,只有在子表中所有相关的行全部删除或重新分配之后,才能删除父表中的纪录,一般对子表也进行删除操作。
强制对强制(M-M)约束

在四类约束:M-MM-OO-MO-O。键值的修改可能会改变表之间的关系,而且可能违反一些约束。违反约束的操作是不允许的。具体的应用必须根据实际的要求和商业规则进行适当的选择。但在设计和开发时,必须考虑所分析的约束。

#47    回复  引用  查看    

2005-05-13 01:26 by magic007     

1、要看开发的这个系统是个什么样的系统,仅仅是一个OLTP系统,或者还要在系统上进行数据分析和数据挖掘。如果仅仅是一个事务处理系统,则以楼主所说的做法,行得通,否则的话,最好不要那样做。
2
、以我的经验,数据冗余在开发上更方便一点,效率可能会高一点,但是后期可维护性和扩展性是比较差的。
3
、关系数据库一个核心的理念应该就是关系了,数据关系。设计数据库,准确的说是设计数据模型,一是保证整个数据模型具有良好的扩展性,也就是说为以后业务的发展,需求的变化提供足够的可扩展性。开发的方便来说应该是次要的,无非就是多点编码。
4
、在楼主的例子中,我从数据关系上谈一下,订单与客户之间是很强的耦合关系,没有客户,那么订单就没有存在的意义。所以肯定是需要主外键关联的。对于付款条款,在业务领域内,对于订单来说是必须具备的,那么也需要主外健关联。比如客户有同名的情况,怎么标识多张相同客户名的订单是同一客户还是不同的客户?客户改名了,如何标识改名前的订单和改名后的订单是同一张订单?为了避免在客户改名后,改名前的订单的客户名变成新的客户名,可以在客户表中建立组合主键,如cust_idcust_seq_id

#48    回复  引用  查看    

2005-06-04 17:27 by 小李程序™     

感觉到有时冗余是有必要的,我相信这里讲的冗余并不是为了提高系统的容错,它也不是我们在这里讨论的范畴。我最关心的就是冗余带来的性能上的提高,因为它极大地减少了在做交集运算的时间。
况且这种冗余完全可以交个我们的DBA去做,我们只关系他提供给我们的数据即可,至于因为数据冗余可能带来的不一致,完全可以用触发器来实现。

#49    回复  引用  查看    

2005-06-07 21:45 by shewo     

你把把客户的名称、联系电话、付款条款名称等订单上要求记录的信息直接COPY到订单的表中,造成了重复存储,重复存储的结果就是数据冗余,冗余的结果很可能就是更新异常,因为有可能你更新了客户表中更新了客户的名称、电话,你必须还要更新订单中的相应信息,反而造成了效率的下降,如果你疏忽忘记更新这些信息了,结果便是数据不一致

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

2005-06-08 08:20 by 听棠.NET     

@shewo
老兄啊。你怎么还在讲这个理论呢,前面的回复你看了吗?我文章的主要目的就是写给你这样的人看的。
一般的人都是以为这样想的,这个谁都知道,我的文章中也指出了,对于象订单这种客观存在的东西,不能死板的搬那种理论。
你说的这种数据不一致,你觉得需要保持吗?已经成为历史的订单,你认为以后因为基本数据的修改,需要全部修改进去吗?那么历史订单怎么办??客户会把已经完成的订单存档作为备案,你的意思是客户还要把以前的订单找出来一个个修改。。最好再理解一下我文章的意思。真没想到,你什么也没看懂。

#51    回复  引用  查看    

2005-06-13 13:20 by skyworht     

第三范式要求是属性不依赖于其它非主属性。我对编程还没入门,只是浏览过几个比较正规的软件。我觉得两种方法都是可行的,不过是应用的环境要因实而选。听棠.NET方法对一些数据量较小、应用中改变较为频繁的数据,比较适用。外键引用,对无效标记的方法在与上相反的情况下应用也许更好。

抛砖引玉,各位指教。

#52    回复  引用  查看    

2005-06-13 21:01 by Peter     

"third normal form" should be used as a guideline to control the database design. I don't think the original design is against "third normal form". To copy extra information to "Order" such as payment term, delivery method etc, I think this is a correct way to do it as the author explained.

When you look at a design and to determine if it follows "third normal form" rule, there is a "time" factor in it. The original setting such as payment term and delivery method are the default setting, which don't determine every single order.

Another way to do it is to design a base level table that has a "time" factor it in.

Just some ideas and I found it's every hard to understand most comments here.

P.S. There is no right and wrong in relational database design, the only crieria is to test it in a real life situation.


P.

#53    回复  引用  查看    

2005-07-01 23:15 by min     

学习。。。

#54    回复  引用    

2005-08-04 15:54 by tx-zero [未注册用户]

终于看了一半,累死我了,呵呵。
看到大家都是在讨论一个问题,就是碰到楼主的情况,
1copy保存,还是2,设置标志的问题。

就我看,还得根据客户业务的需要而决定。

我做过一些系统,由于是关系到一些企业内部比较重要的资料。因此,客户都要求操作要能够恢复,当然恢复是在一定的条件之下的。但是记录客户的操作还是必须的,并且需要记录用户操作了什么,改变了什么数据。

针对我说的这种客户,我大多会采用,做标志,并且copy的方法。
呵呵。
不写了,写得太多,别人看得也累,呵呵。

#55    回复  引用  查看    

2005-08-05 17:46 by kilnt     

学院派设计要死人的,维护冗余数据一样也够死人,这个要权衡了。

另:别看啥大系统,很多大系统细节技术实现很烂的,库结构也很烂。

#56    回复  引用    

2005-08-15 16:08 by nwc [未注册用户]

这个问题讨论很激烈啊!我没什么经验,也想谈点看法。
系统怎么设计?要不要冗余?要多少灵活性?这些都是和用户需要
相关的。上面说到条款的改变和订单的历史性、一致性问题,既然
条款经常要改变,为什么不做一个条款改变的历史记录表,其中有
条款不同版本的有效时间段和实际内容,这样每个订单都有当时的
条款内容可查,只不过还是要加一个时间字段。新的订单就用新的
条款内容。
这种灵活性是增加了,不过复杂度也同样增加了,减少冗余,就要
增加计算的时间。怎样在效率与空间上取得平衡还是要看用户的需
要了。

#57    回复  引用    

2005-08-15 16:39 by nwc [未注册用户]

原来peter说过了,我多嘴了。

#58    回复  引用    

2005-09-28 11:01 by 榴弹炮 [未注册用户]

上面的东西太多了,简单发表一下看法:
1.
范式不是笼子,达不达成,要看情况;
2.
脑子里没有范式的人去谈上面这条,还是补了课再来;
3.
订单上的数据冗余要看情况,如果要保存订单的历史纪录而纪录详细的客户信息,不算违反第三范式,因为客户表内的数据与订单上表的历史纪录意义并不相同,tag的方式来记录历史的客户信息更不违反范式,但要看情况而定.因为历史的客户信息可能与现有的客户信息Code/name相同而违反键的约束,另外,由于单据要进行EDI,保存详细的客户信息更加重要.
4.
纯数据表(不参与EDI与证据纪录)要严格遵守第三范式,否则数据一致性问题会让你知道什么叫做"维护"!这个等式用空间换效率来写太肤浅,应该是用空间+编程错误率+维护效率来换执行效率+编程效率.
5.
再次打倒那些脑子里没有范式的变态份子!我已经快被他们的程序烦死了.

#59    回复  引用    

2005-09-28 12:45 by luckcp [未注册用户]

我认为楼主说的应该是标准引用,而本身就不应该做成外键,所以不违反三范式。
以订单为例:有两个属性分别是客户与付款条例。客户属性应该用外键做,而且尽量使用数据库的外键机制(存在订单引用的客户不能删除,不要设置为级联删除)。客户信息的修改反映到订单是应该的(一条历史订单跟随客户名称的变化而变化对查询是没有影响的,况且客户资料的修改本身就应该谨慎)。付款条例应该做成一个付款标准表(标准引用),订单存储的是付款标准表的实际数据项。

#60    回复  引用  查看    

2005-11-08 23:37 by 纶巾客     

我觉得不违背第三范式也是可以的,一般来说,我们会有一张WasteBook表,记录当时的信息,保存历史数据。这样的话,既可以保证数据库设计的规范,也可以提供灵活性。

#61    回复  引用  查看    

2006-05-07 17:22 by 月色疯狂     

理论就是理论。要么就对,要么就不对。这个没有什么可含糊的。
楼主的问题,实际上是个版本问题。每次条款修改,都会产生一个历史版本。这个历史版本是只读的。并且是不可改变的。可以删除某个条款,但是历史是不能删除的。因为它被引用了。
只要抽象出历史版本的概念,就可以解决楼主所说的问题,并且可以不违反3nf
归根结底,还是楼主对业务的概念抽象得不对,不是3nf的问题。
对于类似的问题,都可以通过引入历史版本的问题。在设计的时候就要考虑是引用对象本身,还是引用对象的历史版本,不要把概念搞错了,更不要以此来怀疑3nf

#62    回复  引用    

2007-01-30 16:16 by 雨恨云愁 [未注册用户]

个人强烈同意
上山砍柴去 的观点

#63    回复  引用    

2007-08-28 09:30 by zhanglin [未注册用户]

第三范式在大的应用系统,想完全遵循是不可能的。

#64    回复  引用    

2008-06-04 02:58 by solidco2 [未注册用户]

我比较赞同2楼的这种做法。如果你要查找用同样的条款下的订单怎么办?也要复制过来吧。
那汇总更不用说了。
我认为数据存入了数据库,除了错误的之外,理论上应该永远不会被删除。就像发生过的事无法抹去一样。你可以把它尘封,但不能把它永远消除。
这一点也许是我从自动编号属性得到的启示。因为,自动编号产生了#1#2,删掉#2,再产生也会是#3。这是因为dbms认为这条记录并没有消失,只是你无法找回了。
扯多了,我确实同意你的结论,不得不违背范式,满足范式的表会很罗索。倒库的方法我其实用的不多,更多的是启用字段,或者状态字段。



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

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

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

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

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

#65    回复  引用    

2008-06-04 03:32 by 还是上面那个人 [未注册用户]

不好意思没有注意已经是几年前的讨论了。
不过还是顺便再提一下,保证是前面讨论中都没有说的:
一个公司在04年叫河北科技有限公司开始下订单。记录了。05年改名为北方科技有限公司,继续下订单。他们是一个公司吗?查找这个公司的全部订单怎么办?
那就是还要记录一个营业执照号码。这个营业执照和ID就是等同地位了,为什么不用ID做一个唯一索引

#66    回复  引用    

2008-12-11 17:30 by 用版本控制来解决 [未注册用户]

月色疯狂说的非常对,
=====================
理论就是理论。要么就对,要么就不对。这个没有什么可含糊的。
楼主的问题,实际上是个版本问题。每次条款修改,都会产生一个历史版本。这个历史版本是只读的。并且是不可改变的。可以删除某个条款,但是历史是不能删除的。因为它被引用了。
只要抽象出历史版本的概念,就可以解决楼主所说的问题,并且可以不违反3nf
归根结底,还是楼主对业务的概念抽象得不对,不是3nf的问题。
对于类似的问题,都可以通过引入历史版本的问题。在设计的时候就要考虑是引用对象本身,还是引用对象的历史版本,不要把概念搞错了,更不要以此来怀疑3nf
=====================

其实这就和我们用cvssubversion等版本控制系统一样,应该让客户表成为一个时间机器,只能进行查询和新增操作,而不可能进行真正的删除和修改操作,我的解决方案是对客户表加三个字段:IDInt类型),SerialID(自增,标记版本),deletemarkboolean),对客户表的修改就是新增一条ID相同的记录,SerialID不同以反映版本,删除就是标记对应IDdeletemark字段。

以上方案可以完美的解决楼主提出的问题,逻辑自洽且不违反范式要求。
同时也能解决
======================
一个公司在04年叫河北科技有限公司开始下订单。记录了。05年改名为北方科技有限公司,继续下订单。他们是一个公司吗?查找这个公司的全部订单怎么办?
那就是还要记录一个营业执照号码。这个营业执照和ID就是等同地位了,为什么不用ID做一个唯一索引
======================
的问题,因为ID不变。

大家以为如何?

 

posted on 2009-06-18 14:16 肥仔 阅读(4513) 评论(2)  编辑 收藏 引用 所属分类: 数据库

评论

# re: 数据库设计之“有时不得不违背的第三范式”[未登录]  回复  更多评论   

用标记来假删除数据都是,提出这个方式的人的一厢情愿。
客户同意吗?你们真正懂客户吗?
很多时候,这种方式客户能接受
但也有很多时候客户不能接受
有些时候客户必须彻底的,永远的删除(有些敏感信息客户在必要的时候必须随时可以删除)。
这个没有绝对的解决方法。
有些人说谓范式就是一定要遵从,那是误认子弟的。
满足用户需求至上。
那些说维护多么困难的人也是白痴(再整洁的数据库设计也需要随着客户的业务变化而变化,你必须要维护,如果维护那么重要,怎么能体现你对于客户的价值呢,能一个系统维护是要收费的,几年后要翻新的,翻新是要赚钱的,连维护都没有,别人还要你干嘛(除了sap买保险式的维保),连维护都没有客户还要翻什么新).

这些理论都是相对的,事实可以证明是他错误的:我没有用第三范式,客户一样认同,系统一样好维护,我一样赚到钱了,客户也通过系统的价值给他带来好处。当然也可以拿出事实证明他是对的,但这就是一个矛盾体。(其实说实在话,大家搞开发都注重前期的实现吧,至于后期的维护有多少人考虑过,如果没有考虑,就不要拿维护难来说事)


从另外一个角度看:
时间+质量+成本 = 项目的管理维目标,如果关系型数据库数据范式第一大于项目管理目标的话(第三范式就是在付出这3个代价的,为了换取你那个所谓的维护容易,存储量小,数据量少,你们都知道:存储硬件不是问题吧,那实在太便宜了,数据难得维护客户一般不会说是系统乱的问题吧,你可以说是业务导致数据复杂难的维护,但性能不好(为了第三方式,必然存在大量的联合查询,如果数据量大查询性能不好的话),客户一定会吊死你的),那不用关系型数据好了。
2009-08-20 23:39 | 无名

# re: 数据库设计之“有时不得不违背的第三范式”[未登录]  回复  更多评论   

冗余这样的方式一般都用于提高系统的性能和增强系统的容错性,并且一般说来,冗余会增加开发的复杂度。在冗余数据之间进行同步是需要耗费精力的,要在开发团体中形成一致的意见,并且要保持后来加入人员认识的统一。这需要严明的纪律和充分的交流。如果一个数据只有一个来源,大家的认识很容易一致。如果一个数据要保存在多个位置,则团体的每个人中对一个数据的来源,更新的时候要不要同步,选取的时候到底从哪里取等一些事项上容易产生分歧,极易造成脏数据。
冗余要有明确的目的,不能贪图一时之便,否则带来的可能是麻烦。第三范式是一个正确的准则,也许有时可以违背,但是一定要有一个更加强的准则作为支持,要考虑具体的情况。这个更强的准则,我想应该不是“可以省一点事”。
【转的】
2011-06-02 18:20 | kevin

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理