逻辑数据库设计 - 可变属性(继承)

  可变属性的需求:我们需要在数据库里面存储很多电器,比如电视,冰箱等等。通常,在程序中,我们的类图为:

EVA设计

  对于这种继承下来的可变属性时,有一种办法是创建另外一张表,将属性当成行来存储。

  

  其中存储的数据类似下面这样:

  

  这样的设计称为:实体-属-值,简称:EVA,或者又叫开放架构、无模式。

  这种设计有如下3种好处:

  1、这两张表的列都很少。

  2、新增的属性不会对现有的表结构造成影响,不需要新增列。

  3、避免由于空值而造成的表内容混乱。

  但是这样也有如下缺点:

  1、查询属性

  本来,我们想要按出厂日期查询,只需要:

  SELECT ElectricId,DateOfManufacture FROM Electric

  但是这种方式不行,它需要这样:

  SELECT ElectricId,AttrValue AS ‘DateOfManufacture‘
  FROM Attribute
  WHERE AttrName = ‘DateOfManufacture‘

  2、无法声明强制属性

  本来,我们要确保DateOfManufacture(出厂日期)这个属性有值,在传统数据库设计中,只需要很简单的声明一个NOT NULL就OK了。

  但是现在在EVA设计中,每个属性对应的是Attribute中的一行。我们需要建立一个约束来检查对于每个ElectricId都存在一行,并且这行的AttrName是DateOfManufacture。并且这行记录的AttrValue不为空,并且符合日期格式。

  3、无法使用SQL的数据类型

  由于AttrValue的格式只能声明为Varchar或NVarchar类型,因此用户输入的日期格式可能是各种各样,甚至有的根本就不是日期格式。

  由于数据类型不能够由限制,因此我们执行如下SQL语句也不会报错。

  INSERT Attribute VALUES(1,‘DateOfManufacture‘,‘我不是一个日期‘)  --这样的语句也不报错

  4、无法确保引用完整性

  加入上面的设计,我们需要添加一个品牌属性。可选值必须是存在的比如,三星,康佳,海尔等等。在传统的数据库设计中,我们只需要设计一张品牌表,并给本表添加一个品牌Id字段,建立外键约束就可以了。

  但是,在EVA设计中,因为品牌属性对应的是一行,因此我们无法使用外键来确保引用完整性。如果我们不处理,那么用户输入的品牌属性的值可能是不存在的。

  5、重复记录

  在EVA设计中,我们可能将同一个属性了两次。

  因为,我们连续执行如下SQL语句两次也是不报错的:

  INSERT Attribute VALUES(1,‘DateOfManufacture‘,‘2013-09-09‘)
  INSERT Attribute VALUES(1,‘DateOfManufacture‘,‘2013-09-10‘)

  由于可能存在重复记录,因此我们按出厂日期统计出厂产品数量也并不可靠。同时,按日期统计,也很复杂。

  SELECT ElcDate, COUNT(*) AS Per_Date
  FROM (SELECT DISTINCT ElectricId,AttrValue AS ElcDate
        FROM Attribute
        WHERE AttrName = ‘DateOfManufacture‘)
  GROUP BY ElcDate

  这是Oracle中的写法。

  6、重组列

  在传统数据库设计中,加入我们要显示一条完整的记录,我们只需要:

  SELECT * FROM Electric

  但是现在,我们要:

  SELECT i.ElectricId,
    i1.AttrValue AS ‘Name‘,
    i2.AttrValue AS ‘DateOfManufacture‘,
    i3.AttrValue AS ‘Screen‘
  FROM Electric AS i
    LEFT OUTER JOIN Attribute AS i1 ON i.ElectricId = i1.ElectricId AND i1.AttrName=‘Name‘
    LEFT OUTER JOIN Attribute AS i2 ON i.ElectricId = i2.ElectricId AND i2.AttrName=‘DateOfManufacture‘
    LEFT OUTER JOIN Attribute AS i3 ON i.ElectricId = i3.ElectricId AND i3.AttrName=‘Screen‘

  不在多说,总而言之,以上的设计,并非一个非常耐得住推敲的设计。

解决方案

  一、单表继承

  单表继承的设计是将所有相关的类型都存在一张表中,为所有类型的所有属性都保留一列。同时使用一个属性来定义每一行表示的子类型。

  例如,对于以上电器的需求,单表继承的数据设计如下:

  

  单表继承的方式可以理解为,所有子类的字段,都往单表里放,存储的时候,当某子实体没有的时候,相应的类为空,都是预留一列作为标记类型。

  单表继承的缺点就是:

  •   列过多。
  •   过多NULL值。
  •   当要增加属性的时候,要改动表结构。

  综上所述:单表继承只是适合使用子类的特殊属性列不多的情况。

  二、实体表继承

  实体表继承可以理解为:子表在设计的时候,将父表的所有的属性全部都在本表定义多一次。

  回到上面的例子,如果用实体表继承的话,对应的设计如下:

  

  实体表继承相比于单表继承,有一个好处,就是防止在一行内存储太多和当前子类型无关的属性。比如在冰箱表里没有了屏幕列,而在单表继承中,是由Scree列的NULL值的。另外,也不用在加多一个列用于标记当前是什么电器。

  实体表继承的致命缺点:

  重复列过多

  重复列过多,很容易让人摸不着头脑。

  三、类表继承

  我的推荐,我最喜欢,我认为最可靠的方式

  类表继承模拟了高级程序语言中的继承,把表当成面向对象里的类。创建一张基表,包含所有子类型的公共属性。对于每个子类型,创建一个独立的表,通过外键和基类表相连。

  对以以上例子,类表继承的设计如下:

  

  类表继承,相比于实体类继承,明显的有点在于,少了很多重复列。子类表中,主键同时也是外键。

  我认为这是一个比较好的方法。

  四、半结构化

  半结构化,实际上跟单表继承差不多。单表继承是多个列,而半结构化使用一个新特性,比如一个xml类型的列,来存储子类的属性。

  对于以上例子,半结构化的设计如下:

  

  子类的信息,存在一个XML列中,你爱设置什么节点就什么节点。反正查询起来也不麻烦。不够要记住的是,要有一个Type列,来标记哪行是哪种电器。不然就全乱套了。

  由于,现在SQLServer对XML的支持越来越强大,这也是一个不错的选择。

时间: 2024-08-09 14:59:44

逻辑数据库设计 - 可变属性(继承)的相关文章

逻辑数据库设计 - 单纯的树(递归关系数据)(转)

逻辑数据库设计 - 单纯的树(递归关系数据) 相信有过开发经验的朋友都曾碰到过这样一个需求.假设你正在为一个新闻网站开发一个评论功能,读者可以评论原文甚至相互回复. 这个需求并不简单,相互回复会导致无限多的分支,无限多的祖先-后代关系.这是一种典型的递归关系数据. 对于这个问题,以下给出几个解决方案,各位客观可斟酌后选择. 一.邻接表:依赖父节点 邻接表的方案如下(仅仅说明问题): CREATE TABLE Comments( CommentId int PK, ParentId int, --

逻辑数据库设计 - 需要ID(谈主键Id)

本文的目标就是要确认那些使用了主键,却混淆了主键的本质而造成的一种反模式. 一.确立主键规范 每个了解数据库设计的人都知道,主键对于一张表来说是一个很重要,甚至必需的部分.这确实是事实,主键是好的数据库设计的一部分.主键是数据库确保数据行在整张表唯一性的保障.它是定位到一条记录并且确保不会重复存储的逻辑机制.主键也同时可以被外键引用来建立表与表之间的关系. 难点是选择那一列作为主键.大多数表中的每个属性值都有可能被很多行使用.例如姓名,电子邮件地址等等都不能保证不会重复. 在这样的表中,需要引入

ABP 初探 之User、Role、Permission数据库设计 (EntityFramework 继承的另一种使用方法)

最近群里(134710707)的朋友都在讨论ABP源码,我把最近学习的内容记录下来,同时也分享给大家,希望正在研究ABP源码的朋友有一定帮助. 上篇介绍ABP的多语言,本篇主要介绍权限的数据库设计,用EntityFramework已经有段时间了,基于ABP这样的设计还是第一次看到,具体应用场景1:N,ABP权限设计,菜单的权限可以分配置给角色,也可以直接分配给用户. 另一个应用场景也可以是订单系统:客户可以通过订单查询到客户的所有订单明细,订单明细与客户没有关系,如果想直接查看客户的订单明细,也

逻辑数据库设计 - 无视约束(谈外键)

有一些开发人员不推荐使用完整性约束,你可能听过以下这么几点不使用外键的原因. 1.数据更新有可能和约束冲突. 2.当前的数据库设计如此灵活,以致于不支持引用完整性约束. 3.数据库为外键建立的索引会影响性能. 4.当前使用的数据库不支持外键. 5.定义外键的语法并不简单,还需要查阅. 一.反模式:无视约束 即使第一感觉告诉你,省略外键约束能使得数据库设计更加简单.灵活,或者执行更加高效,你还是不得不在其他方面付出相应的代价 -- 必须增加额外的代码来手动维护引用完整性. 1.完整性问题 很多人对

逻辑数据库设计 - 多列属性(多列转行)

假设有一个要开发一个试题系统,全是不定项选择题.一道题可能有2,3,4...个答案,数据应如何设计呢?本处旨在说明问题所在,例如同类问题还有存储电话,一个人可能有多个号码等等. 一.存储多值属性 反模式:创建多个列. 我们知道每列最好只存储一个值,因此先看如下设计: CREATE TABLE Question( QuestionId int PK, QuestionBody nvarchar(500), Answer1 nvarchar(500), Answer2 nvarchar(500),

逻辑数据库设计 - 单纯的树(递归关系数据)

相信有过开发经验的朋友都曾碰到过这样一个需求.假设你正在为一个新闻网站开发一个评论功能,读者可以评论原文甚至相互回复. 这个需求并不简单,相互回复会导致无限多的分支,无限多的祖先-后代关系.这是一种典型的递归关系数据. 对于这个问题,以下给出几个解决方案,各位客观可斟酌后选择. 一.邻接表:依赖父节点 邻接表的方案如下(仅仅说明问题): CREATE TABLE Comments( CommentId int PK, ParentId int, --记录父节点 ArticleId int, Co

逻辑数据库设计 - 多态关联

多态关联 先说明什么是多态关联. 假设我们有一张地址表,其中的地址可能是对于User中的,也可能是对于Orders中的. 以上,只是举个例子,实际的例子还有很多,比如我们要设计一个内容管理系统(CMS),我们的CMS有一个文章表,一个软件表.还要求支持评论,那么我们的评论表的Id是引用文章表还是引用软件表呢? 对于以上例子的缺点,貌似书本上有故意为此多态关联的模式走软的嫌疑.缺点不说了,主要是查询麻烦,其次不能够支持外键约束. 解决方案 交叉表 对于这种需要外键引用为多个表的情况,可以建立一张交

逻辑数据库设计 - 元数据分裂(分区表)

我之前曾参与维护过一个舆情监控系统,该系统每天源源不断地监控着互联网上的新闻,不断从网上下载新闻保存进入数据库. 提出问题 为了表述简单,我特意模拟了一张类似的表: CREATE TABLE NEWS( Id int PK, Title nvarchar(500) --新闻标题 Content text --新闻内容 CreateTime DateTime) 随着时间的推移,数据库里的新闻变得越来越多,系统开始跑得越来越慢.随后,技术经理考虑到,舆情监控需要的仅仅是近期的数据,过时的数据,不太重

逻辑数据库设计 - 乱穿马路(多对多关系)

一.乱穿马路模式介绍 程序员通常使用逗号分隔的列表来避免在多对多关系中创建交叉表,这种设计方式定义为一种反模式,称为乱穿马路. 例如:在一个产品管理系统中,一个人可以有多个产品,一个产品必须对应一个人,因此有如下数据库: 但是,随着时间的推移,出现了一个产品可能会有多个联系人.于是为了最小限度地修改数据库,可能不少人会将Account_Id的类型修改成varchar,这样就可以列出该列中的多个账号Id,每个Id之间用逗号分隔.这样的设计貌似可行,因为并没有创建额外的表或者列.仅仅改变了一个字段的