【如此简单!数据库入门系列】之理解数据的钥匙 -- 函数依赖


问题

考虑一个在线商店数据库,其中包含以下表:

【订单表】

Order_Num(订单号)Product_ID(产品ID)Count(数量)Price(单价)
1102100
2101100
3203200

你觉得这张表是否存在问题?存在哪些问题?
在这里插入图片描述

  1. 数据不一致。同一产品可能有多个不同的单价,导致计算错误。例如,因写入失误,订单1和订单2中产品10的单价不一致,导致后续统计结果出错。
  2. 数据冗余。数据冗余除了可能导致数据不一致的问题之外(问题1),还存在空间浪费的问题。例如,每条包含产品10的订单,都会存储单价信息。

应该怎么解决这两个问题呢?

  • 首先要消除冗余数据,让每个产品的单价只存储一份。假设存在另一张产品单价表,保存每个产品的单价,如下所示:

【单价表】

Product_ID (产品ID,主键)Price(单价)
10100
20200
  • 然后剔除订单表中的单价列,并将产品ID作为外键,如下所示:

【优化后的订单表】

Order_Num(订单号,主键)Product_ID(产品ID,外键)Count(数量)
1102
2101
3203
  • 通过Product_ID外键关联订单表和单价表,可以解决数据冗余和数据不一致问题。

函数依赖

刚才的问题通过消除冗余数据和使用外键就能解决。有没有想过,解决方案背后的原理是什么呢?

核心在于,同一个产品始终有相同的单价(只要两个元组的产品ID相同,它们的产品单价也必须相同)。

一个属性决定另一个属性,这种约束就称为函数依赖。

函数依赖(Functional Dependency,FD)

  • 是关系中两个属性之间的一组约束
  • 表示如果两个元组在属性A1, A2,…, An上有相同的值,那么这两个元组必须在属性B1, B2,…, Bn上也有相同的值
  • 函数依赖用箭头符号(→)表示,即X→Y,其中X函数决定Y。左侧的属性决定了右侧属性的值

简单来说,函数依赖X→Y,可以理解为相同的X一定有相同的Y。例如,同一个产品始终有相同的单价,表示为:{产品ID} → {产品单价}。


函数依赖的作用

函数依赖有什么作用?或者,为什么需要函数依赖?

例如,根据{产品ID} →{单价},可以将订单表{#订单号,产品ID,数量,单价},拆分成两个表:

  • 单价表{#产品ID,单价},产品ID作为主键,确保每个产品ID是唯一的
  • 订单表{#订单号,产品ID,数量},订单号作为主键,产品ID作为外键,关联到单价表

这么做有以下作用。

1. 保证数据一致性

  • 插入一致性:当插入一个新的产品记录时,只要产品ID已经存在,系统将不允许插入,因为这会违反函数依赖,保证每个产品的单价信息是唯一的。
  • 更新一致性:如果需要更新产品单价信息,只需通过产品ID定位到特定的单价记录进行更新。如果没有这样的函数依赖,可能会出现同一个产品的多条记录拥有不同的信息,导致数据不一致。
  • 删除一致性:同理,删除操作也可以通过产品ID精确地移除某个产品单价信息,而不会影响其他产品单价数据。

2. 减少数据冗余

这种设计减少了数据的冗余存储,因为单价信息只存储一次,而不是在每个订单记录中重复存储。

3. 优化查询性能

例如,想查询产品单价,直接从单价表中查询即可,性能非常好(主键等值查询)。而在原始的订单表中查询某个产品的单价,,则需要执行过滤和去重操作。

4. 设计规范化的数据库

函数依赖是数据库设计规范化的基础方法(下篇文章介绍数据库规范化内容)。


函数依赖的性质

我们进一步讨论函数依赖具备的性质。

阿姆斯特朗公理(Armstrong’s Axioms)

阿姆斯特朗公理是一组规则,包括:

  1. 自反规则(Reflexive rule):如果α是一个属性集,β是α的子集,那么α→β。例如,如果我们有属性集{A, B},那么根据自反规则,我们可以得出函数依赖{A} → {A}和{B} → {B},因为每个属性自己就是自己的子集。

  2. 增广规则(Augmentation rule):如果a → b成立,并且y是一个属性集,那么ay → by也成立。也就是说,在依赖中增加属性,不会改变依赖关系。例如,如果我们有函数依赖{A} → {B},并且我们有另一个属性C,那么根据增广规则,我们可以得出函数依赖{AC} → {BC}。

  3. 传递规则(Transitivity rule):与代数中的传递规则相同,如果a → b成立并且b → c成立,那么a → c也成立。例如,如果我们有函数依赖{A} → {B}和{B} → {C},那么根据传递规则,我们可以得出函数依赖{A} → {C}。

平凡函数依赖(Trivial Functional Dependency)

函数依赖可以分为三种情况:

  • 平凡 :如果一个函数依赖 X → Y成立,其中Y是X的子集,那么它被称为平凡函数依赖。平凡函数依赖总是成立的。
  • 非平凡 : 如果 X → Y成立,其中Y不是X的子集,那么它被称为非平凡函数依赖。
  • 完全非平凡 : 如果 X→Y成立,其中X与Y交集为空,则称为完全非平凡函数依赖。

自反规则和平凡函数依赖密切相关:

  • 假设属性集{A,B},根据自反规则,{A,B} → {B}
  • 因为{A,B} → {B},所以是平凡函数依赖

阿姆斯特朗公理和平凡函数依赖有什么用?

  • 作为其它函数依赖的基础
  • 用于推导出新的隐式的函数依赖

重新理解主键

函数依赖是一种表模式设计的基础方法,与主键有密切关系(一个属性如何决定其他属性),如何从函数依赖的角度理解主键?

从函数依赖的角度,可以这样理解主键:

  • 表模式定义为:{X,Y},X和Y分别表示属性集(包含1~N个属性)
  • 假设,X→Y成立,且Y包含除了X以外的所有其他属性,那么X就被认为是一个超键
  • 如果X没有冗余,即不能再移除任何属性而保持其唯一性,则X可认为是候选键,可当选为主键

总结

通过函数依赖,我们知道应该如何分解关系模式,以保证数据一致性并消除数据冗余 — 即如何进行数据库规范化设计。

在规范化过程中,我们使用函数依赖来:

  • 识别候选键。候选键必须满足以下条件:
    • 候选键中的属性对关系中的所有其他属性都有函数依赖关系。
    • 候选键中的属性没有冗余。
  • 分解关系。一旦识别了候选键,就可以使用函数依赖来分解关系。分解涉及将关系拆分为多个较小的关系,每个关系都包含候选键。这有助于消除冗余并确保数据完整性。

更多有关数据库规范化的内容,我们下次再接着讨论。


系列文章


如果喜欢这篇文章,请不要忘记关注、点赞和收藏哦!
您的鼓励将是我创作的最大动力!

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

架构师昌哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值