老翅寒暑

一个老鸟的自白
随笔 - 79, 文章 - 0, 评论 - 674, 引用 - 24
数据加载中……

两难的境界:不定字段数目的数据库表设计和数据结构

两难的境界:不定字段数目的数据库表设计和数据结构

昨天项目组会议上讨论的关于不定字段数目的数据库表问题并没有结果,今天继续分析之后发现问题可能还更大。当时讨论的结果是可能采用四种技术:

  1. 动态增加数据库表字段
  2. 预留足够的空白字段,运行时作动态影射
  3. 用xml格式保存在单字段里
  4. 改列为行,用另外一个表存放定制字段

现在我们来分析一下四种技术的优劣,不过首先可以排除的是第一点动态增加字段的方法,因为在实际操作时候几乎是不可能的(sqlserver太慢,oracle索性不支持),基本可以不讨论就排除。剩下后三点。

先来讨论预留空白字段的方法,基本原理就是在数据库表设计的时候加入一些多余的字段,看下面的代码:

CREATE TABLE Sample(
  name 
varchar(12),
  
  field0 
varchar(1),
  field1 
varchar(1),
  
  fieldN 
varchar(1)
}

然后看实际运行时候的需要,动态分配字段给系统使用,也许需要一个这样的结构来描述分配情况:

public class Available
{
  
public int CurrentUnusedFieldNumber;
  
public Hashtable FieldToRealName;
}

也许某一时刻的数据状况是这样的: CurrentUnusedFieldNumber=3, 哈西表FieldToRealName包含内容是("field0"="SomeId", "field1"="AnyName", "field2=IsOk")

现在的问题是如果要配合Hibernate,如何来处理?以上段的数据使用状况为例子,如果我们的类定义是这样:

public class Entity01
{
  
public string Name;
  
public string SomeId;
  
public string AnyName;
  
public bool IsOk;
}

也许只需要修改一下xxx.hbm.xml,把 SomeId 和 field0 做成对应就ok了。但是在运行时我们怎么知道会有这样的类定义?除非我们做动态代码生成,自动编译也许可以,但是问题也许就到其他方面去了;如果我们不用动态定义,那么类就只能是这样:

public class Entity01
{
  
public string Name;
  
public Hashtable ExtraFieldAndValues;
}

使用的时候,用 entity01.ExtraFieldAndValues.setValue("AnyName", "boss") 的方式来引用,也许这样是修改最少的了,但是问题是Hibernate不支持这样的方法。

再来讨论单字段存储的方法,我们使用这样的数据库表定义

CREATE TABLE Sample
(
  Name 
varchar(12),
  Xml CLOB(
102400// 仅作说明而已
)

然后对应这样的类定义

public class Entity01
{
  
public string Name;
  
public string Xml;

  
public Hashtable ExtraNameAndValueFromXml;
}

我们的代码就可以这样使用:string id = entity01.ExtraNameAndValueFromXml.getValue("SomeId") 了。这样解决看起来很不错,不仅不需要Available表,而且看起来Hibernate对它的支持也很完美,但是致命的问题在于:如果保持高效的查询?除非数据库系统本身对此有支持,否则就只能用低效的substring或者like做查询,这在大批量数据中根本就不可行。

是不是折衷一下,把两种方法的优点和起来?问题有来了:怎么保持两者之间数据的同步?难道要我们用存储过程去解析xml内容?

所以,一个两难的问题,需要我们认真去解决。我们通过认真的需求分析,也许可以减少可变字段的数量,但是只要有一个可变字段或者可变的可能性存在,我们始终要去解决这个两难的问题。

期待继续讨论。

(新增部分)

还有一种方法就是改列为行,用另外一个表存放扩展字段,定义可以如下:

CREATE TABLE SampleFields
(
  idSample 
Integer,
  fieldName 
varchar(30),
  fieldValue 
varchar(100)
)

其中idSample关联到Sample表的id字段(我没有写出来)。这样的话,Hibernate很容易支持,也可以支持Sql的查询,而且可以支持把内容放到Hashtable中去,看起来是目前最好的方式了。但是在大容量数据的时候,SampleFields表的数据会是主表数据量的N倍(看定制的字段数目多少而定),同样存在有很严重的性能问题。

哪位高人还能再给一个方案?

---------2005-7-22新增-----------
很多朋友给出了很好的建议,其中蛙蛙池塘 给出了一个表结构,因为看起来不甚方便,我把类图画出来如下:
asp.net电子商务高级编程.gif
图所表示的内容简单来说是这样:
1。一个很宽的表ProductAttributeValues,包含用到的几乎所有可能的类型的值,但是每次只能用一个类型的值
2。将可变的列转为行,存放到上表中
3。为了存放类型定义和一些下拉列表的内容,设计了ProductAttributeTemplates和关联的其他表
4。ProductListItems中存放Product中所有项的说明和顺序。

这种思路其实就是把产品的“知识级”(设计模式用语)和“操作级”都表现出来了,如果要划分,则图的左上角三个表属于“操作级”,其余的属于“知识级”。wljcan 网友建议我去看《设计模式》的“观察模式”,我发现上图其实就是一种和“观察模式”相似的设计。《设计模式》看了很久都没能看下去,不过这几天正在看“观察模式”,等有心得了,再来看看能不能对上图的结构修改一下。

怡红公子 提醒说oracle和sql server对xml字段其实已经不错了,所以找了一下,但是真的是既产品中恐怕还是不敢用,不知道性能如何。虽然采用xml方式是我最推崇的方法。而且一旦采用xml方式存储,恐怕就会有changyu 网友提醒的数据类型问题,要存一个图片的话恐怕就歇菜了(当然也不是说xml中就一定不能存图片)。

不过我现在觉得xml字段还是要的,作为辅助存储手段,因为毕竟未来查询起来可能会更方便些,然后结合“观察模式”也就是类似上图的方法作为主要的存储手段,虽然有一些冗余,结合我的使用 NVelocity 解析 PowerDesigner 的cdm文件 ,工作量也不会太大。

再研究研究看看。

posted on 2005-07-15 14:00 老翅寒暑 阅读(5607) 评论(42)  编辑 收藏 网摘 所属分类: 概念与理解

评论

#1楼   回复  引用  查看    

这种问题常见了,除了需要动态添加字段,还要显示,验证等。
我现在的做法是用xml定义表单样式,然后动态生成界面。
由于有较好的维护工具,维护起来相当容易。
xml定义的表单,以控件的形式。
大部分只需要一个aspx就可以了,部分特殊的,可以新建aspx,然后
拖入这个控件,添加一些事件处理就差不多了。
很多人反对我这样的做法,但是按照现在我的环境,不得不采用。
以前搞java的时候,公司有一套系统,用java模拟.net的dataset,
以实现方便添加字段的功能。不过界面方面使用stringbuilder来拼写,
导致几乎不能扩展。
2005-07-15 14:10 | neuhawk      

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

问题是界面还好说,关键就是我们的数据量非常非常大,千万级的数据。而且我们想用Hibernate这样的ORM作为数据层接口。我们现在就是要用java来做。
2005-07-15 14:20 | bigtall      

#3楼   回复  引用  查看    

代码中的图片显示不出来, 建议调整一下。
2005-07-15 14:21 | dudu      

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

代码中没有图片阿
2005-07-15 14:23 | bigtall      

#5楼   回复  引用  查看    

Hibernate 2.x确实没有实现这个功能,Hibernate 3我就不太清楚了。
java不是有蛮多脚本语言,如BeanShell, Groovy,好想可以动态修改
代码(没有用过)。
2005-07-15 14:35 | neuhawk      

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

动态修改代码带来的问题更大,系统都不知道它有哪些多余的字段,怎么能去利用动态代码生成的那些字段哦,如果再用反射去设置/获取其中的值,岂不是要让大家笑掉了牙。所以我还不如放到Hashtable里,好歹还可以iterator一把。
2005-07-15 15:24 | bigtall      

#7楼   回复  引用    

我支持第一种方案:动态增加数据库表字段,这是最适合的
2005-07-15 16:12 | mini[未注册用户]

#8楼   回复  引用    

在有索引的情况下,第4种方式是最好的
另外,SQL2005和Oracle 10G对XML field的支持都不错,虽然FLOWR支持不全,但是也很够用了
2005-07-15 16:42 | 怡红公子[未注册用户]

#9楼   回复  引用    

建议使用第三种方面,用XML保存多列的数据在一列里面...
这样相比第一种方案,性能会好
相比第二种方案,更灵活一点
相比第四种方案,简洁一些...

呵呵...
2005-07-15 16:54 | 非非.net[未注册用户]

#10楼   回复  引用  查看    

楼主 没有 使用过 列 行 互变的方法?
2005-07-15 16:58 | DoItNow      

#11楼   回复  引用    

在 Martin Fowler的《分析模式》中,提到了这个问题,他建议用“观察和测量模式”来解决(与楼主所说的第四种差不多)。我个人也认为这方案是最简洁,不过性能相对1、2 会差一些。(应该和第三种差不多)


采用哪种方案,主要看应用场合,如果对性能要求高的话,可以采用第二种方案,一这种场合下就最好别用Hibernate了,Hibernate并非所有场合都适用的。

我个人建议使用第4种方案。
2005-07-15 17:06 | wljcan

#12楼   回复  引用    

不知道搂主是否有没有数据类型这一因素考虑进去,方案3、4支持的数据类型很有限。方案2可以修改一下支持更多的类型,但是预留字段也是有限的,总有用完的时候。
2005-07-15 17:12 | changyu[未注册用户]

#13楼   回复  引用    

如果你是广州的可以来我公司看看,我们目前做的应该可以借鉴一下。具体细节不方便在网上透露。
2005-07-15 17:20 | 来福[未注册用户]

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

to changyu: 数据类型我也考虑了,类型方面方案1最好,方案2、3、4其实各有千秋,既然都是用字符串,也就无所谓了。

怡红公子给的建议很有用,我看了一下,oracle的写法是这样的:
SELECT XMLELEMENT("Emp", XMLATTRIBUTES ( e.fname ||' '|| e.lname AS "name" ), XMLForest ( e.hire, e.dept AS
"department")) AS "result" FROM employees e;

看起来有些意思,但是不知道性能如何。

回家再想想有没有更好的办法
2005-07-15 17:34 | bigtall      

#15楼   回复  引用  查看    

234 都 会给查询增加难度
2005-07-15 17:41 | Microshaoft      

#16楼   回复  引用  查看    

这个问题讨论起来比较有意思。

第四种方式应该比较好,性能当然会差一些,但我想不至于无法忍受,不同意楼主“严重性能问题”的论断。灵活性与性能之间本来就是一个平衡关系。实在不行的时候,可能通过透视表的方式定义视图,反正检索数据的时候是不需要修改数据的。

方案四主要优点在于:
一、几乎可以无穷扩展;
二、对查询没有太大的影响,可以动态添加一些不能Save的域对象来支持表现层;
三、扩展字段的表在写数据时可以通过单独的域对象来操作。

我个人不主张采用不确定类型的动态字段搞得数据库操作太复杂。我建议将数据的动态化放到另外一个动态模块中,每个动态的模块负责维护自己这一块确定的数据访问及对表现层的支持。反正每一次变动你都必须重新部署,这样变动的部分通过插件方式解决,不变的问题怎么都不会变。所以我最倾向于已经被你否定的方案一。
2005-07-15 18:42 | 双鱼座      

#17楼   回复  引用  查看    

不建议使用第一种,觉得维护太难了
第二种用在比较简单的应用,理解和查询都不会太复杂也便于维护,但是不方便扩充
第三种方法很好理解也容易扩充和维护,但是查询太难,对于不要求查询的或者你自己能做查询引擎的可以考虑
第四种对字段类型好象有些限制吧,如果全部考虑用字符型代替,如数字等其他用不到索引,还有就是ntext,image等没办法存储,一般查询尚可

的确这几种都各有千秋,找一个各方面都满意的很难,如果ms或oracle能够做些工作,xml方案比较有潜力的,关注ing
2005-07-15 22:05 | ttyp      

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

对于是否使用Hibernate的问题,我看到有人推荐其他的产品,比如 iBATIS 等 [http://www.javaeye.com/viewtopic.php?t=14348">http://www.javaeye.com/viewtopic.php?t=14348]。

如 ttyp 所说,一旦要存储照片,方案4就有麻烦了,方案3也会有麻烦。
2005-07-16 09:39 | bigtall      

#19楼   回复  引用    

还是认为 行列转换 更容易实现一些,或者说我认为
不定字段数目的数据库表 不合理
2005-07-16 18:54 | dcs[未注册用户]

#20楼   回复  引用    

和顶楼的一样,我也是用XML做描述,动态生成界面.
对于每个应用建立字段映射即可,GRUD方法自动完成.
故我是推荐2,3两种方法的结合。
2005-07-18 08:53 | yoyo

#21楼   回复  引用  查看    

窃以为要么就别用Hibernate之类的ORM,要么就结合动态编译使用.

一般而言,对于这些数据库结构需要扩展的需求,采用O/R Mapping组件,会有许多麻烦事产生.

上面有朋友说,不定字段数目的数据库表不合理.确实是这样的,不过,这不既不是软件设计者的问题,也不是关系理论的问题,而是现今大多数关系数据库的自身问题.
2005-07-18 09:13 | 寒枫天伤      

#22楼   回复  引用  查看    

第四种方案其实很麻烦的,我用过类似的方法,由于变列为行,数据的约束处理必须都由自己来完成。还由涉及数据的n多的问题。
2005-07-18 09:38 | poweword      

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

关于“不定字段数目的数据库表不合理”的问题:目前有N种合同,每一种合同中的具体条款都不一样(也就是系统计算所要求使用到的合同相关的参数),如何来设计一个“固定字段数目的数据库表”呢?难道用N个表不成?
2005-07-18 13:03 | bigtall      

#24楼   回复  引用  查看    

我以前在CSDN问过这样的问题,结果一个回帖也没有,想不到你们发到这里,有这么多人讨论,CSDN和博客园简直不是一个级别呀,以后不去CSDN了,真是的,那些人光想着要分.我见过一个不定字段数目的数据结构的解决办法是用了一个模板表,我给你贴一下代码哦
2005-07-18 13:05 | 蛙蛙池塘      

#25楼   回复  引用  查看    

CREATE TABLE [dbo].[ProductAttributeDataTypes] (
 [DataTypeId] [int] NOT NULL ,
 [Description] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
)
CREATE TABLE [dbo].[ProductAttributeListItems] (
 [LookupListId] [int] NOT NULL ,
 [ListItemId] [int] IDENTITY (1, 1) NOT NULL ,
 [DisplayName] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
)
CREATE TABLE [dbo].[ProductAttributeLookupLists] (
 [LookupListId] [int] IDENTITY (1, 1) NOT NULL ,
 [Name] [varchar] (80) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
)
CREATE TABLE [dbo].[ProductAttributeTemplateCategories] (
 [PATCategoryId] [int] IDENTITY (1, 1) NOT NULL ,
 [Name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
 [DisplayName] [varchar] (255) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
)
CREATE TABLE [dbo].[ProductAttributeTemplates] (
 [AttributeTemplateId] [int] IDENTITY (1, 1) NOT NULL ,
 [Name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
 [DataType] [int] NOT NULL ,
 [LookupListId] [int] NULL ,
 [PATCategoryId] [int] NOT NULL ,
 [CustomerHelp] [varchar] (4000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
 [DisplayName] [varchar] (255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
)
CREATE TABLE [dbo].[ProductAttributeValues] (
 [ProductId] [int] NOT NULL ,
 [AttributeTemplateId] [int] NOT NULL ,
 [valueInt] [int] NULL ,
 [valueStr] [varchar] (255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
 [valueMemo] [text] COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
 [valueBool] [bit] NULL
)
CREATE TABLE [dbo].[ProductListItems] (
 [ProductListId] [int] NOT NULL ,
 [ProductId] [int] NOT NULL ,
 [ItemDescription] [varchar] (80) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
 [DisplaySequence] [int] NOT NULL
)
 
不知道大家有没有看懂这些表之间的关系,这是一个产品目录的表,产品有不确定的属性,每个属性对应的数据类型也不一样,而且有的是用下拉列表选择的,有的是 是或否,有的是一段儿文字,这里用的就是数据模板来做的,这是<asp.net电子商务高级编程>里的一部分数据库
2005-07-18 13:17 | 蛙蛙池塘      

#26楼   回复  引用  查看    

博客园大家发的文章偶大多看不懂,偶还是好好学习去吧
2005-07-18 13:22 | 蛙蛙池塘      

#27楼   回复  引用  查看    

??
试过类似继承的方法吗??

一个表是另一个表的基表(基类)...............

这样一般的情况应该可以了。
2005-07-18 14:36 | 审判者      

#28楼   回复  引用  查看    

这个问题,我认为要看需求,如果是动态字段,一般情况下应该是些不常用的,或用户自已在现在的条件下,自已扩展的字段,这个时候需要用户自已去维护它,如果要提供索引,查询等功能,做起来就比较费力,不如用简单的办法搞定,能做到扩展数据能自定名称,类型,数据能保存,修改,载入。

如果这样,用3还是比较简单的,4也可行。
2005-07-18 17:11 | DSharp      

#29楼   回复  引用    

不用动态创建表也可以,
Oracle中就那几种数据类型,估计你使用的也就三四种,

根据第4种方法“ 改列为行,用另外一个表存放定制字段”

进行改良:

改列为行, 每种类型预先建一个表即可。
2005-07-19 17:03 | 独行大侠[未注册用户]

#30楼   回复  引用  查看    

跑题的回复:

希望文中不要将“设计模式”与“分析模式”混为一谈:)
2005-08-06 09:33 | 吕震宇      

#31楼   回复  引用    

偶用方法4.

一个表保存字段
一个表保存数据

做了一个类似excel表格的东西 字段随便增加 数据也随便增加 但没考虑数据类型.





2005-10-17 19:49 | aimi.cn[未注册用户]

#32楼   回复  引用    

我觉得如果动态生成对象,然后将对象进行序列化,以二进制方式保存到数据库中的一个字段不就可以解决吗。
1动态生成对象没问题。
2保存到数据库也没问题
3最大的问题在于,如果这些字段是要查询用的。那麻烦可就来了。很不方便
2005-11-02 12:44 | greystar[未注册用户]

#33楼   回复  引用    

楼主提到的问题,是比较困惑的难题:
我目前在做的一个项目,表名是动态的、字段也时动态(有一定规则),动态并不是随意动态,是在一定业务规则下,通过设置创建的。
这种情况用硬编码,没什么问题,只是复杂一点,有许多项目已经在正式运行,性能也与固定表一样的。
现在想采用O/R方式持久化,减少硬编码,遇到了楼主提到的问题,我认为第一种是真实有效的解决方法,对于Hibernate中如何应用,是否有变通方法:
1、动态创建表及增删字段时,同步生成hbm.xml及.java文件,生成这些文件相信没有任何问题.J2EE项目中,发布后是一个*.WAR或*.EAR文件,如何发布是一个大问题!
2、扩充Hibernate?提供动态表的定义与解析...
3、其它变通方法?
希望大家继续讨论,关注中。
2006-03-08 17:13 | Ramadr[未注册用户]

#34楼   回复  引用  查看    

建议搂主研究一下 微软的 Sharepoint 它就是干这个的
2006-04-21 20:43 | 小李程式™      

#35楼   回复  引用    

地四种方法可以直接排除。不管看起来多么优雅,数据量大了性能问题就出来了。
架设100个字段,10万条数据,很普通吧。结果要存储一千万条记录。而且这种多表关联也是低效的,数据库底层做迪卡尔运算是非常低效地。
我推荐第五种方法....

#36楼   回复  引用    

用xml格式保存在单字段里,如果不用sqlserver2005和ora10g,你跟本无法使用xml类型字段,而且使用xml查询语法也比较麻烦。这样你就无法跨数据库了。如果只是以普通xml文本形势存放到varchar里,你又无法通过sql查询。而且这样做效率也高不了。排除之。。。。。。。

#37楼   回复  引用    

动态增加数据库表字段 ,要求用户需要有dba权限,这个也可能不现实。而且动态增加表字段,你的所有sql都要动态跟上,比如update ,insert等。而且确实很多数据库都不一定支持。

预留足够的空白字段,运行时作动态影射 虽然是个简单的笨办法,不过算这四个方法里相对好点的了。因为效率高。不过加字段也得有时有晌阿,你加多少个算合适呢?加多少个算够呢?方法不通用。

#38楼   回复  引用  查看    

这么久了 有没人持续关注啊?
2008-03-26 10:46 | WhyCome[at]live.cn       

#39楼   回复  引用  查看    

lz还在么?
说说实际的进展如何
2008-03-26 10:47 | WhyCome[at]live.cn       

#40楼   回复  引用  查看    

补充一点:

方案一如果在视图中引用了这个表的话,Oracle可能有点问题,Oracle的视图和存储过程是需要编译的,如果动态增加了列,视图可能需要重新编译才行。

方案三、四需要做统计报表时就不太方便了。
2008-03-27 16:56 | Niels      

#41楼   回复  引用  查看    

最后讨论的结果是怎样呢?
2008-05-05 12:23 | 零缺陷生活      

#42楼   回复  引用    

这个东西还是看需求了,本人现在就准备用xml结构来解决不确定字段的问题,麻烦的是需要自己来解决xml的解析(多种情况下的不同方式解析),查询没打算用数据库来做,lucene搜索能解决这个问题,因此,估计最后的实际效果不会很烂吧,呵呵,期待高人指点。
2008-06-18 10:28 | superlee[未注册用户]



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 193401




相关文章:

相关链接: