09 / 数据库:把事实存得清楚、查得准确 · 小节 1
关系、键与关系操作
从现实对象建表,并解释每张表一行代表什么。
大纲要求的教学展开 · 技术讲解 · 本节来源与考点
从一个问题开始
把姓名当图书借阅人的唯一标识,会发生什么?
实体、联系与外键
读者和实体册分别有唯一编号。
Loan每行代表一次借阅,保存两侧外键。
同一读者可有多条历史借阅;姓名不能代替读者键。
原创教学示意 · 手动播放,可随时暂停;折叠或离开小节时停止。
把这件事讲清楚
关系可直观看作表,属性是列,元组是行,域是允许的值。候选键是能唯一识别元组的最小属性组;选择一个作主键。外键引用另一表的候选唯一标识,维护引用完整性。实体完整性要求主键唯一且非空。SQL表通常允许重复行,不能把数学集合的去重规则无条件套到SQL。
关系选择筛行,投影选列,连接按相关属性组合行。NULL表示缺失或未知,并不等于0或空串;SQL用IS NULL判断,不用= NULL。约束与权限、业务逻辑共同维护有效数据。
为什么成立 · 关键推导
从“一个人可借多本,一本实体册可先后被多人借”推出借阅记录必须独立成表,保存读者、册号、日期。一行代表一次借阅,不是一个读者。书名对应作品,条码对应实体册,应按业务粒度分开。
入门例题 / 1
Reader(id,name)、Copy(id,title)、Loan(id,reader_id,copy_id,returned)。两位同名“小林”靠id区分,Loan.reader_id外键指向Reader.id,避免借给不存在的人。
典型应用与变式 / 2
要查未归还借阅人的姓名:先从Loan选择returned=0的行,再连接Reader的id,最后投影name。若同一人有两次未还记录,结果可能重复,是否去重取决于问题问“记录”还是“人”。
停一下,自己试一试
先独立作答;卡住时看提示,完成后再对照解析。
概念判断
外键必须在本表唯一吗?
需要一点提示
许多订单能引用同一客户。
查看过程、答案与错因
不必。外键常重复;被引用的键要能唯一确定目标。
计算与执行过程
读者1有借阅10、11,读者2有借阅12,连接后几行?
需要一点提示
每条借阅匹配一个读者。
查看过程、答案与错因
3行。若只问有借阅的读者数,应数不同reader_id,答案2。
条件与错误辨析
删除仍被借阅表引用的读者可随意执行吗?
需要一点提示
如何保持引用完整性?
查看过程、答案与错因
应按预设限制、级联或其他规则处理,不能留下无法解释的悬空引用;历史借阅是否保留要由需求决定。
带走这一句
先确定“一行是什么”,再选择键和连接条件。
09 / 数据库:把事实存得清楚、查得准确 · 小节 2
从查询到分组:读懂SQL
能在纸上执行查询,区分筛行与筛组。
大纲要求的教学展开 · 技术讲解 · 本节来源与考点
从一个问题开始
想找借了至少2本未还书的人,为什么不能直接WHERE COUNT(*)>=2?
把这件事讲清楚
SQL是声明式查询语言:说明要什么结果,数据库选择执行方式。SELECT选输出列,FROM指定来源,JOIN…ON连接,WHERE先筛行,GROUP BY分组,HAVING筛组,ORDER BY排序。COUNT(*)计行;COUNT(column)忽略该列NULL;SUM、AVG等一般忽略NULL。逻辑理解顺序与书写顺序不同。INSERT新增,UPDATE修改,DELETE删除;写操作缺少WHERE可能影响所有行。事务把多步作为一个整体,成功提交COMMIT,失败回滚ROLLBACK。
为什么成立 · 关键推导
固定数据Loan=(id,reader_id,returned):(10,1,0),(11,1,0),(12,2,1),(13,2,0)。先WHERE returned=0剩10、11、13;按reader_id分组得到1有2、2有1;HAVING COUNT(*)>=2仅保留1。事务的原子性防止借阅记录写入而库存未扣这种部分成功。
入门例题 / 1
SELECT reader_id, COUNT(*) AS n FROM Loan WHERE returned=0 GROUP BY reader_id HAVING COUNT(*)>=2;
结果一行:reader_id=1,n=2。n是输出列别名,不是新增存储字段。
典型应用与变式 / 2
Reader=(1,小林),(2,小周),(3,小陈)。LEFT JOIN Loan可保留无借阅的3。统计每人借阅次数应COUNT(Loan.id),结果1→2,2→2,3→0;COUNT(*)会把3的补NULL行也计1。
停一下,自己试一试
先独立作答;卡住时看提示,完成后再对照解析。
概念判断
WHERE与HAVING可以不加区别互换吗?
需要一点提示
一个作用于行,一个作用于组。
查看过程、答案与错因
不能。聚合后的条件应HAVING;聚合前过滤可改变分组输入。
计算与执行过程
固定数据中COUNT(*)、COUNT(DISTINCT reader_id)各多少?
需要一点提示
完整Loan有四行,两位读者。
查看过程、答案与错因
分别4和2。若先筛未还,则行数3但不同读者仍2。
条件与错误辨析
把用户输入直接拼进SQL字符串安全吗?
需要一点提示
输入可能被解释为语法。
查看过程、答案与错因
不安全。应用应使用参数化查询并配权限;字符串转义不能代替完整的安全设计。
带走这一句
先筛行,再分组,再筛组;输出几行要能手算。
09 / 数据库:把事实存得清楚、查得准确 · 小节 3
函数依赖、规范化与ER设计
发现更新异常,把一张混杂表拆成有约束的关系。
大纲要求的教学展开 · 技术讲解 · 本节来源与考点
前置知识:从查询到分组:读懂SQL · 符号回看
从一个问题开始
每条借阅记录都重复存读者电话,换号码要改多少行?
实体、联系与外键
读者和实体册分别有唯一编号。
Loan每行代表一次借阅,保存两侧外键。
同一读者可有多条历史借阅;姓名不能代替读者键。
原创教学示意 · 手动播放,可随时暂停;折叠或离开小节时停止。
把这件事讲清楚
函数依赖X→Y表示相同X必有相同Y,是业务约束,不是一次数据巧合。1NF要求每个属性值在当前模型中不可再分;2NF在1NF上消除非主属性对候选键的部分依赖;3NF消除相应传递依赖(正式判据为每个非平凡依赖X→A,X是超键或A为主属性);BCNF进一步要求所有非平凡依赖的决定因素为超键。主属性指属于某候选键的属性。
规范化减少插入、删除、更新异常,分解必须考虑无损连接与依赖保持,表越多不一定越好。ER图表示实体、属性和联系;1对多常在多侧放外键,多对多通常另建联系表。
为什么成立 · 关键推导
流程:需求与业务规则→概念ER模型→关系模式与键→依赖与分解→约束/索引/权限→验证典型查询。先明确实体粒度,例如“课程”与“某学期某班开课”可能不同,不能只凭名词建表。
入门例题 / 1
表(学生号,课程号,学生名,成绩),键(学生号,课程号),学生号→学生名。学生名只依赖键的一部分,违反2NF。拆Student(学生号,学生名)和Enroll(学生号,课程号,成绩)。共同学生号是Student键,可无损重连。
典型应用与变式 / 2
表(员工号,部门号,部门电话),员工号是键,部门号→部门电话,电话经部门传递依赖员工号。拆Employee与Department,改电话只改一处;仍须外键保证员工引用的部门存在。
停一下,自己试一试
先独立作答;卡住时看提示,完成后再对照解析。
概念判断
当前数据中姓名没有重复,就能声明姓名→学号吗?
需要一点提示
依赖应在所有合法状态成立。
查看过程、答案与错因
不能。还需业务规则保证,未来同名会破坏该依赖;数据观察只是线索。
计算与执行过程
作者与书多对多,给关系表及键。
需要一点提示
联系自身也要记录。
查看过程、答案与错因
Author(author_id,…)、Book(book_id,…)、Authorship(author_id,book_id),联系表以二者组合为键并各设外键;如需署名次序另加属性和约束。
条件与错误辨析
任意把表一分为二都可无损恢复吗?
需要一点提示
连接可能制造伪元组。
查看过程、答案与错因
不能。须核对共同属性的决定关系等条件;例如拆出两张只靠非唯一姓名相连的表可能产生错误组合。
带走这一句
规范化依据业务依赖,设计完成后还要验证能否正确连回。
章末 / 把知识接起来
综合训练
原创综合训练
Reader(id,name),Loan(id,reader_id,returned)。设计外键;写每位读者未还数量;解释为何不在每行Loan重复电话。
需要一点提示
LEFT JOIN的筛选放ON可保留零借阅者;COUNT计右表非空键。
查看过程、答案与错因
外键Loan.reader_id引用Reader.id。SELECT r.id, COUNT(l.id) AS n FROM Reader r LEFT JOIN Loan l ON l.reader_id=r.id AND l.returned=0 GROUP BY r.id; 电话属于读者事实,重复会产生更新异常,应放Reader。WHERE l.returned=0会筛掉补NULL的零记录读者。