1、没有系统学习过数据结构的程序员。这类程序员的作品往往只是他们的即兴玩具,他们往往习惯只设计有限的几个表,实现某类功能的数据全部塞在一个表中,各表之间几乎毫无关联。网上不少的免费管理软件都是这样的东西,当程序功能有限,数据量不多的时候,其程序运行起来没有什么问题,但是如果用其管理比较重要的数据,风险性非常大。
2、系统学习过数据结构,但是还没有开发过对程序效率要求比较高的管理软件的程序员。这类人多半刚从学校毕业不久,他们在设计数据库表结构时,严格按照教科书上的规定,死扣 E-R 图和 3NF( 别灰心,所有的数据库设计高手都是从这一步开始的 ) 。他们的作品,对于一般的 access 型轻量级的管理软件,已经够用。但是一旦该系统需要添加新功能,原有的数据库表差不多得进行大换血。
3、第二类程序员,在经历过数次程序效率的提升,以及功能升级的折腾后,终于升级成为数据库设计的老鸟,第一类程序员眼中的高人。这类程序员可以胜任二十个表以上的中型商业数据管理系统的开发工作。他们知道该在什么样的情况下保留一定的冗余数据来提高程序效率,而且其设计的数据库可拓展性较好,当用户需要添加新功能时,原有数据库表只需做少量修改即可。
4、在经历过上十个类似数据库管理软件的重复设计后,第三类程序员中坚持下来没有转行,而是希望从中找出 “ 偷懒 ” 窍门的有心人会慢慢觉悟,从而完成量变到质变的转换。他们所设计的数据库表结构有一定的远见,能够预测到未来功能升级所需要的数据,从而预先留下伏笔。这类程序员目前大多晋级成数据挖掘方面的高级软件开发人员。
5、第三类程序员或第四类程序员,在对现有的各家数据库管理系统的原理和开发都有一定的钻研后,要么在其基础上进行二次开发,要么自行开发一套有自主版权的通用数据库管理系统。
不少程序员在进行数据库设计的时候都遇到过树型关系的数据,例如常见的类别表,即一个大类,下面有若干个子类,某些子类又有子类这样的情况。当类别不确定,用户希望可以在任意类别下添加新的子类,或者删除某个类别和其下的所有子类,而且预计以后其数量会逐步增长,此时我们就会考虑用一个数据表来保存这些数据。按照教科书上的教导,第二类程序员大概会设计出类似这样的数据表结构:
名称 类型 约束条件 说明
type_id int 无重复 类别标识,主键
type_name char(50) 不允许为空 类型名称,不允许重复
type_father int 不允许为空 该类别的父类别标识,如果是顶节点的话设定为某个唯一值
总类别
类别 1
类别 1.1
类别 1.1.1
类别 1.2
类别 2
类别 2.1
类别 3
类别 3.1
类别 3.2
……
名称 类型 约束条件 说明
type_id int 无重复 类别标识,主键
type_name char(50) 不允许为空 类型名称,不允许重复
type_father int 不允许为空 该类别的父类别标识,如果是顶节点的话设定为某个唯一值
type_layer char(6) 限定 3 层 , 初始值为 000000 类别的先序遍历,主要为减少检索数据库的次数
1 总类别 0 000000
2 类别 1 1 010000
3 类别 1.1 2 010100
4 类别 1.2 2 010200
5 类别 2 1 020000
6 类别 2.1 5 020100
7 类别 3 1 030000
8 类别 3.1 7 030100
9 类别 3.2 7 030200
10 类别 1.1.1 3 010101
……
1 总类别 0 000000
2 类别 1 1 010000
3 类别 1.1 2 010100
10 类别 1.1.1 3 010101
4 类别 1.2 2 010200
5 类别 2 1 020000
6 类别 2.1 5 020100
7 类别 3 1 030000
8 类别 3.1 7 030100
9 类别 3.2 7 030200
……
假设你是一家百货公司电脑部的开发人员,某天老板要求你为公司开发一套网上电子商务平台,该百货公司有数千种商品出售,不过目前仅打算先在网上销售数十种方便运输的商品,当然,以后可能会陆续在该电子商务平台上增加新的商品出售。现在开始进行该平台数据库的商品信息表的设计。每种出售的商品都会有相同的属性,如商品编号,商品名称,商品所属类别,相关信息,供货厂商,内含件数,库存,进货价,销售价,优惠价。你很快就设计出 4 个表:商品类型表 (Wares_type) ,供货厂商表 (Wares_provider) ,商品信息表 (Wares_info) :
名称 类型 约束条件 说明
type_id int 无重复 类别标识,主键
type_name char(50) 不允许为空 类型名称,不允许重复
type_father int 不允许为空 该类别的父类别标识,如果是顶节点的话设定为某个唯一值
type_layer char(6) 限定 3 层 , 初始值为 000000 类别的先序遍历,主要为减少检索数据库的次数
名称 类型 约束条件 说明
provider_id int 无重复 供货商标识,主键
provider_name char(100) 不允许为空 供货商名称
名称 类型 约束条件 说明
wares_id int 无重复 商品标识,主键
wares_name char(100) 不允许为空 商品名称
wares_type int 不允许为空 商品类型标识,和 Wares_type.type_id 关联
wares_info char(200) 允许为空 相关信息
provider int 不允许为空 供货厂商标识,和 Wares_provider.provider_id 关联
setnum int 初始值为 1 内含件数,默认为 1
stock int 初始值为 0 库存,默认为 0
buy_price money 不允许为空 进货价
sell_price money 不允许为空 销售价
discount money 不允许为空 优惠价
名称 类型 约束条件 说明
pic_id int 无重复 商品图片标识,主键
wares_id int 不允许为空 所属商品标识,和 Wares_info.wares_id 关联
pic_address char(200) 不允许为空 图片存放路径
名称 类型 约束条件 说明
length_id int 无重复 商品图片标识,主键
wares_id int 不允许为空 所属商品标识,和 Wares_info.wares_id 关联
length char(20) 不允许为空 商品长度说明
名称 类型 约束条件 说明
ex_pid int 无重复 商品额外属性标识,主键
p_name char(20) 不允许为空 额外属性名称
名称 类型 约束条件 说明
ex_iid int 无重复 商品额外信息标识,主键
wares_id int 不允许为空 所属商品标识,和 Wares_info.wares_id 关联
property_id int 不允许为空 商品额外属性标识,和 Wares_ex_property.ex_pid 关联
property_value char(200) 不允许为空 商品额外属性值
ex_pid p_name
1 商品图片
2 商品长度
开发 数据库 管理类的软件,不可能不考虑多用户和用户权限设置的问题。尽管目前市面上的大、中型的后台数据库系统软件都提供了多用户,以及细至某个数据库内某张表的权限设置的功能,我个人建议:一套成熟的数据库管理软件,还是应该自行设计用户管理这块功能,原因有二:
1. 那些大、中型后台数据库系统软件所提供的多用户及其权限设置都是针对数据库的共有属性,并不一定能完全满足某些特例的需求;
2. 不要过多的依赖后台数据库系统软件的某些特殊功能,多种大、中型后台数据库系统软件之间并不完全兼容。否则一旦日后需要转换数据库平台或后台数据库系统软件版本升级,之前的架构设计很可能无法重用。
功能表 (Function_table)
名称 类型 约束条件 说明
f_id int 无重复 功能标识,主键
f_name char(20) 不允许为空 功能名称,不允许重复
f_desc char(50) 允许为空 功能描述
名称 类型 约束条件 说明
group_id int 无重复 用户组标识,主键
group_name char(20) 不允许为空 用户组名称
group_power char(100) 不允许为空 用户组权限表,内容为功能表 f_id 的集合
名称 类型 约束条件 说明
user_id int 无重复 用户标识,主键
user_name char(20) 无重复 用户名
user_pwd char(20) 不允许为空 用户密码
user_type int 不允许为空 所属用户组标识,和 User_group.group_id 关联
四、简洁的批量 m:n 设计
碰到 m:n 的关系,一般都是建立 3 个表, m 一个, n 一个, m:n 一个。但是, m:n 有时会遇到批量处理的情况,例如到图书馆借书,一般都是允许用户同时借阅 n 本书,如果要求按批查询借阅记录,即列出某个用户某次借阅的所有书籍,该如何设计呢?让我们建好必须的 3 个表先:
名称 类型 约束条件 说明
book_id int 无重复 书籍标识,主键
book_no char(20) 无重复 书籍编号
book_name char(100) 不允许为空 书籍名称
……
名称 类型 约束条件 说明
renter_id int 无重复 用户标识,主键
renter_name char(20) 不允许为空 用户姓名
……
名称 类型 约束条件 说明
rent_id int 无重复 借阅记录标识,主键
r_id int 不允许为空 用户标识,和 Renter_table.renter_id 关联
b_id int 不允许为空 书籍标识,和 Book_table.book_id 关联
rent_date datetime 不允许为空 借阅时间
……
名称 类型 约束条件 说明
batch_id int 无重复 批量借阅标识,主键
batch_no int 不允许为空 批量借阅编号,同一批借阅的 batch_no 相同
rent_id int 不允许为空 借阅记录标识,和 Rent_log.rent_id 关联
batch_date datetime 不允许为空 批量借阅时间
名称 类型 约束条件 说明
rent_id int 无重复 借阅记录标识,主键
r_id int 不允许为空 用户标识,和 Renter_table.renter_id 关联
b_id int 不允许为空 书籍标识,和 Book_table.book_id 关联
batch_no int 不允许为空 批量借阅编号,同一批借阅的 batch_no 相同
rent_date datetime 不允许为空 借阅时间
……
五、冗余数据的取舍
上篇的 “ 树型关系的数据表 ” 中保留了一个冗余字段,这里的例子更进一步 —— 添加了一个冗余表。先看看例子:我原先所在的公司为了解决员工的工作餐,和附近的一家小餐馆联系,每天吃饭记账,费用按人数平摊,月底由公司现金结算,每个人每个月的工作餐费从工资中扣除。当然,每天吃饭的人员和人数都不是固定的,而且,由于每顿工作餐的所点的菜色不同,每顿的花费也不相同。例如,星期一中餐 5 人花费 40 元,晚餐 2 人花费 20 ,星期二中餐 6 人花费 36 元,晚餐 3 人花费 18 元。为了方便计算每个人每个月的工作餐费,我写了一个简陋的就餐记账管理程序,数据库里有 3 个表:
名称 类型 约束条件 说明
clerk_id int 无重复 员工标识,主键
clerk_name char(10) 不允许为空 员工姓名
名称 类型 约束条件 说明
totle_id int 无重复 每餐总表标识,主键
persons char(100) 不允许为空 就餐员工的员工标识集合
eat_date datetime 不允许为空 就餐日期
eat_type char(1) 不允许为空 就餐类型,用来区分中、晚餐
totle_price money 不允许为空 每餐总花费
persons_num int 不允许为空 就餐人数
名称 类型 约束条件 说明
id int 无重复 就餐计费细表标识,主键
t_id int 不允许为空 每餐总表标识,和 Eatdata1.totle_id 关联
c_id int 不允许为空 员工标识标识,和 Clerk_table.clerk_id 关联
price money 不允许为空 每人每餐花费
2、简化开发的复杂度。现代软件开发,实现同样的功能,方法有很多。尽管不必要求程序员精通绝大部分的开发工具和平台,但是还是需要了解哪种方法搭配哪种开发工具的程序更简洁,效率更高一些。冗余数据的本质就是用空间换时间,尤其是目前硬件的发展远远高于软件,所以适当的冗余是可以接受的。不过我还是在最后再强调一下:不要过多的依赖平台和开发工具的特性来简化开发,这个度要是没把握好的话,后期维护升级会栽大跟头的。
1万+



被折叠的 条评论
为什么被折叠?



