数据库表怎么设计?走进范式原则

数据库范式

        在设计数据库表结构时,是无法脱离数据库三范式原则的,对于初级程序员来说,这一节的内容至关重要。它是规范化的一种方法,用于设计具有高度结构化和标准化的数据库。常见的数据库范式包括第一范式(1NF)、第二范式(2NF)、第三范式(3NF)和Boyce-Codd范式(BCNF)。每个范式都有其特定的规则和原则,以确保数据的正确性一致性完整性

        第一范式(1NF):要求每个属性都是原子性的,即不可再分解。表必须具有唯一的主键,以确保每个行都具有唯一的标识符。

        第二范式(2NF):要求每个非主键属性都完全依赖于主键,不存在部分依赖

        第三范式(3NF):要求每个非主键属性都不依赖于其他非主键属性,不存在传递依赖

        Boyce-Codd范式(BCNF):第三范式的扩展,要求每个属性都完全依赖于主键,不存在任何属性决定了其他非主键属性。这种范式适用于具有多个复合主键的表。


第一范式表设计

        现在开始实践一下,我们假设现在需要设计学生信息表,每个学生有一个唯一的身份证号、学号、姓名、年龄、性别、家庭地址、班级信息。那么此时按照一范式的设计原则,我们这里会出现几张表?

        首先从扩展性来看:其中家庭地址班级信息这两个属性都不具备原子性,因为家庭地址可能会存在:省、市、区、街道、门牌号、邮箱等信息。班级信息可能会存在:班级标识、所属班主任、班级名称或者其它的信息。因此按照一范式的设计原则来看,这里至少需要三张表。具体如下:

学生表
字段名数据类型说明
id        INT        主键Id
nameVARCHAR(50)学生姓名
ageINT学生年龄
genderENUM('0','1')学生性别(0:女;1:男)
class_idINT所属班级ID
address_idINT详细地址ID
班级表
字段名数据类型说明
idINT (PRIMARY KEY)主键ID
class_nameVARCHAR(50)班级名称
class_teacher_idINT教师ID
地址信息表
字段名数据类型说明
idINT (PRIMARY KEY)主键ID
provinceVARCHAR(50)省份名称
cityVARCHAR(50)城市名称
areaVARCHAR(50)区县名称
streetVARCHAR(50)街道名称
postboxVARCHAR(50)邮箱号

       那么性别呢?它同样存在了‘男’、‘女’这两种可能,需要拆分为一张单独的表吗?

        答案是不需要,因为性别只是一个简单的属性,也就是非女即男,可以使用Enum类型来限制性别的列,以此来保证这个字段不会被输入其它值。

        以上这样做我们就保证了每张表中的数据原子性。也就达成了一范式的设计原则。同时这也是业界广泛应用的设计方式。

        原子性:指关系数据库中的每一列都只包含一个数据项。也就是说,如果一个列需要包含多个数据项,那么它应该被拆分成多个列。


第二范式表设计

        再来看看二范式的设计原则。现在假设业务场景是这样子:有一家超市希望自己的货物可以通过订货的方式发送到客户所在地,并且通过订货单进行货物出售记录。这时我们会需要几张表来完成这个任务?

        先来看一个错误的例子。假设我们的订货单如下:

订货单
字段名数据类型说明
customer_idvarchar(50)客户 ID
customer_namevarchar(50)客户名字
customer_addressvarchar(100)客户所在地址
customer_phonevarchar(20)客户电话号码
customer_ordervarchar(20)客户订单号
customer_oder_timedate订单日期
total_amountdecimal(10,2)订单总金额

        这会出现一个什么样的问题?我们假设行数据如下:

客户 ID客户名字客户所在地址客户电话号码客户订单号订单日期订单总金额
CUS001客户一号xx省xx市xx区xx街道173****7662OD0012023-03-234,398
CUS001客户一号xx省xx市xx区xx街道173****7662OD0022023-03-245,398
CUS002客户二号xx省xx市xx区xx街道136****4432OD0032023-03-233,356

        我们可以看到CUS001号客户的信息,好像连续出现了两次,这次为什么?因为它连续两天都需要订货。所以这里我们可以看到以日期为基准,出现了客户信息的重复。

        这就会导致大面积数据冗余。比如此时CUS001号客户的电话号码发生了改变,这就会需要对订货表的记录进行逐行更新。这样做就是违背了数据库第二范式的原则。

        所以最好的办法就是将订货表和客户信息拆分开来。比如:

订货表
字段名数据类型说明
order_idvarchar(50)订单ID
customer_idvarchar(50)客户 ID
order_datedate订单日期
total_amountdecimal(10,2)订单总金额

        我们可以看到,客户表的客户ID为主键,其它字段都完全依赖于客户ID。订货表的订单ID为主键,其它字段都完全依赖于订单ID。且也已满足第一范式。数据列都具备了原子性。

        这样设计的好处就在于,避免了数据冗余提高了数据的一致性和可维护性。


第三范式表设计

        通过上面两个范式,我们可以看出来,范式之间是逐层递进的,它们之间存在着一定的层次关系。第范式= 第范式的进一步规范化,第三范式=第二范式的进一步规范化。

        以二范式为基准,我们独立拆分了订货表和客户表.如下:

订货表
字段名数据类型说明
order_idvarchar(50)订单ID
customer_idvarchar(50)客户 ID
order_datedate订单日期
total_amountdecimal(10,2)订单总金额

        那么按照实际的业务中,我们可能会还想要知道这个订单对什么物品购买了多少数量。那么为了确保每个表都是独立的、原子的数据项。我们的表该如何进行扩展?

        加表,在我们前面的第一二范式下,表的列已经具备了原子性,也就是不可再分。如果我们需要对表进行扩展,且需要满足第三范式,那么最好的做法就是添加附属信息表。

        例如新增两张表:Order_Items(订单明细表)、Products_Info(产品信息表)

订单明细表
字段名数据类型说明
order_idvarchar(50)订单ID
product_idvarchar(50)产品ID
product_numint(11)订购数量
产品信息表
字段名数据类型说明
product_idvarchar(50)产品ID
product_namevarchar(50)产品名称
product_create_timedate产品生产日期
product_end_timedate产品到期日期
product_pricedecimal(10,2)产品单价

        这样做无疑是符合三范式的要求,每个表都只包含独立的、原子的数据项没有重复的数据,也没有冗余的数据。


总结

        在日常的数据库设计过程中,范式越高就一定越好吗

        当然不是,范式的递增随之而来的很明显的直观感觉就是,表的拆分变得更细,这会导致我们在查询数据的时候,可能需要多次的Join才能得到最终的数据,这无疑是加大了查询的成本

        其次就是增加了数据存储、更新、删除的复杂度。若不使用强关联(主外键形式),还有可能导致数据错乱

        有时候数据的冗余并不会对我们产生多大的影响,比如一些创建人、创建时间、更新人、更新时间这样的字段,冗余是没有问题的。

        所以在实际的表设计过程中,我们需要对数据的复杂度查询频率数据未来可能的扩展性等因素去选择合适的范式进行设计。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值