简介:本资源是《数据库系统概论(第5版)》王珊、萨师煊主编教材的配套课后习题详细答案,面向高校计算机、软件工程、信息管理等专业本科生及数据库初学者,用于巩固关系模型、关系代数、SQL语言等核心理论知识,辅助课程学习与期末备考。文件为单个Word文档(.doc),共1份,大小588KB,内容覆盖第2章关系数据库与第3章SQL语言全部重点习题,包括关系模型三要素、完整性约束判定、等值连接与自然连接辨析、SPJ关系代数表达式推导、SQL建表与多表查询语句编写等典型题型,并附有清晰解题步骤与关键注释。已有1723人学习下载,答案严格对应教材章节逻辑,术语规范、推导严谨,可直接用于作业参考、考前梳理与概念自查,是理解关系数据库底层原理与提升SQL实战能力的实用学习材料。
1. 别被“数据库系统概论”吓住:它不是天书,是你手边那本能查、能练、能抄答案的“关系代数练习册”
你刚点开这份叫《数据库系统概论第5版答案(王珊版)》的文档,心里可能嘀咕:“这名字怎么像大学期末考前夜在打印店狂敲的PDF?全是‘关系模型’‘范式’‘参照完整性’……我连SQL SELECT都写不利索,看这个有啥用?”
别急。先说个大实话:这份文档根本不是教材,也不是讲义,它是一份“带解题过程的课后习题标准答案集”——而且是专为零基础初学者设计的“脚手架式答案”。
它解决的问题特别具体:当你对着课本第二章那道“求供应工程J1零件的供应商号码SNO”的题发懵时,它不跟你讲什么叫“投影π”,而是直接给你写出πSno(σJno=‘J1’(SPJ)),再一行行翻译成“先从SPJ表里挑出JNO='J1'的行(这是选择σ),再只留下SNO这一列(这是投影π)”;当你被“等值连接和自然连接的区别”绕晕时,它用一句话点破:“等值连接是‘手动指定用哪个字段相等’,自然连接是‘自动找名字相同、类型相同的字段,连完还把重复列删掉’——就像你微信加好友输手机号(等值),和扫对方二维码自动通过(自然)。”
它适合谁?适合三类人:
- 正在啃王珊《数据库系统概论》第5版、但卡在“关系代数符号看不懂”“SQL嵌套查询写不出来”的本科生;
- 准备软考中级数据库系统工程师、想快速过掉“关系理论+SQL语法”基础模块的在职人;
- 想转行做数据分析或后端开发,需要补上“数据库底层逻辑”这一课、但讨厌纯理论空讲的自学者。
它不承诺让你一夜成为DBA,但它能让你今天下午就搞懂“为什么外码可以为空”“为什么视图不能随便UPDATE”,并亲手把课本P42那道SPJ四表联查题跑通。接下来,我们就从最扎手的第一关开始:不背定义,先动手画出你的第一个关系图。
2. 先搞懂一个概念:关系模型不是抽象理论,而是你每天都在用的“Excel多表联动”
很多新手一看到“关系模型由关系数据结构、关系操作集合、关系完整性约束三部分组成”就头皮发麻。别急着记这三句话——我们把它拆成你手机里正在用的功能。
2.1 关系数据结构:就是你熟悉的“表格”,但比Excel更较真
想象你用Excel管理班级通讯录:
- A列是学号(唯一标识一个人),B列是姓名,C列是班级号。
- 这张表本身,就是“关系数据结构”里的一个关系(Relation),也就是我们常说的“一张表”。
- 表头A/B/C这些列名,叫属性(Attribute);每一行数据,叫一个元组(Tuple);整张表的结构定义(比如“学号是2位字符,不能为空”),叫关系模式(Relation Schema)。
提示:课本里说“关系是笛卡尔积的子集”,听着玄乎。其实就等于说:“这张表的所有行,必须是从所有可能组合中筛选出来的合法数据”。比如你班只有30人,那这张表就只能有30行,不能凭空冒出第31行——这就是“结构”在管事。
2.2 关系操作集合:不是命令行,而是“对表格动手指的5种基本动作”
课本第二章开头问:“关系代数的基本运算有哪些?”答案是并、差、笛卡尔积、投影、选择。别被名字吓退,它们对应的是你每天都在做的5件事:
| 运算名 | 中文意思 | 类比Excel操作 | 课本例题中的样子 | 白话解释 |
|---|---|---|---|---|
| 选择(σ) | 筛选行 | Ctrl+F找“城市=天津” | σJno=‘J1’(SPJ) | “从SPJ表里,只留下JNO这一列值等于'J1'的所有行” |
| 投影(π) | 提取列 | 只保留A列和C列 | πSno(σJno=‘J1’(SPJ)) | “先把上面筛出来的行,再只取SNO这一列” |
| 笛卡尔积(×) | 两表暴力拼接 | 把表1每行和表2每行配对 | S × P | “S表有3行,P表有4行,结果就是12行(3×4),每行是S的一行+P的一行” |
| 并(∪) | 合并去重 | 把两个Sheet的数据粘一起,删重复行 | R ∪ S | “把R表和S表的所有行合起来,重复的只留一份” |
| 差(−) | 剔除交集 | 用表1减去表2里有的行 | R − S | “R表里有,但S表里没有的那些行” |
你会发现:所有复杂查询,都是这5个动作的组合。比如课本P23第6题第4问:“求没有使用天津供应商生产的红色零件的工程号JNO”,答案是πJno(SPJ) − πJNO(σcity=‘天津’∧Color=‘红‘ (S∞SPJ∞P))。
拆开看:
- 先算右边:
σcity=‘天津’∧Color=‘红‘ (S∞SPJ∞P)→ 从S、SPJ、P三表连接结果里,找出“城市=天津且颜色=红”的记录; - 再
πJNO→ 只取这些记录的JNO列; - 左边
πJno(SPJ)→ 取SPJ表里所有JNO; - 最后“左边减右边” → 所有JNO里,去掉那些“被天津红零件污染过”的工程号。
这根本不是数学推导,而是一套清晰的“数据清洗流水线”。
2.3 关系完整性约束:不是枷锁,而是你建表时自动开启的“防错开关”
很多人觉得“完整性约束”是DBA才管的事。错。它其实是你在CREATE TABLE时,悄悄按下的几个安全按钮:
实体完整性:主键不能为空、不能重复。
就像你填身份证号——系统强制要求你必须填,而且全世界不能有两个一样的。
课本答:“若属性A是基本关系R的主属性,则属性A不能取空值”。实操提醒:建S表时
Sno C(2) UNIQUE,这个UNIQUE就是实体完整性的落地。参照完整性:外键必须指向真实存在的主键,或为空。
就像你填“部门编号”,系统会检查:这个编号在DEPT表里真的存在吗?如果不存在,就拦住你。
课本答:“外码属性的值可以为空,但前提是它本身不是主属性”。
举个生活例子:你填简历,“曾任职公司”这一栏可以空着(没工作过),但“身份证号”不能空(它是你的主键)。用户定义完整性:你自定义的业务规则。
比如“职工年龄不得超过60岁”,课本P127第6题里用CONSTRAINT C1 CHECK (Age<=60)实现。
这相当于在Excel单元格里设“数据验证”:只允许输入0-60的数字,超了就弹窗报错。
这三类约束,本质是同一套逻辑:用声明式规则代替人工校验。你不用每次INSERT前自己写代码检查“这个SNO在S表里有没有”,数据库替你做了。
2.4 避坑:新手写关系代数和建表时最容易翻车的5个地方
现象 → 原因 → 解决,一条一条来,全是血泪经验:
现象:用关系代数写“求供应红色零件的供应商”时,写了
πSno(σCOLOR=‘红’(P)),结果查出来一堆空。
原因:P表里只有零件信息(PNO, PNAME, COLOR),没有供应商SNO!你漏掉了SPJ表这个“桥梁”。
解决:牢记“供应商→零件”不是直连,中间必须过SPJ表。正确写法是πSno(σCOLOR=‘红’(P∞SPJ)),先让P和SPJ连接,再筛选红色。现象:SQL建表时复制课本答案
CREATE TABLE S ( Sno C(2) UNIQUE,Sname C(6) ,Status C(2),CityC(4)),执行报错“Unknown data type C”。
原因:课本用的是教学简化语法(C代表Character),真实数据库(MySQL/PostgreSQL)不认。
解决:把C(2)改成CHAR(2),C(6)改成VARCHAR(6)。这是新手第一道坎,跨过去就海阔天空。现象:写完
SELECT * FROM SPJ,P WHERE SPJ.PNO=P.PNO,结果行数爆炸(比如SPJ有100行,P有10行,结果1000行)。
原因:忘了加WHERE条件,导致笛卡尔积全量生成。课本P58第3题第4问就踩这个坑。
解决:永远检查JOIN条件是否写全。用EXPLAIN(MySQL)或EXPLAIN QUERY PLAN(SQLite)看执行计划,如果显示“CROSS JOIN”,立刻回头补ON。现象:给视图VSP插入数据
INSERT INTO VSP VALUES('S1','P1',100),报错“Can't insert into a view”。
原因:课本P79明确说“基本表的行列子集视图一般是可更新的”,但VSP是SELECT SNO,SPJ.PNO,QTY FROM SPJ,J WHERE ...—— 它涉及JOIN和WHERE,属于不可更新视图。
解决:视图只用来查,增删改必须操作底层SPJ表。这是原则,不是bug。现象:执行
UPDATE P SET COLOR='蓝' WHERE COLOR='红'后,发现有些红色零件没变蓝。
原因:字符串比较区分大小写或空格。课本答案写COLOR='红',但实际数据可能是'红 '(带空格)或'RED'(英文)。
解决:先用SELECT COLOR, LENGTH(COLOR) FROM P WHERE COLOR LIKE '%红%'查真实存储格式,再针对性UPDATE。宁可多查一次,别信眼缘。
3. 先跑通第一个SQL:用课本SPJ四表,5分钟写出你的第一条“真正能执行”的查询
光看关系代数符号还是虚的。现在,我们用真实数据库(推荐SQLite,免安装,单文件)把课本P42第6题的查询跑通。目标:不抄答案,自己从建表开始,到查出结果,全程可复现。
3.1 第一步:创建四张表(照着课本答案改,但要改对数据类型)
打开SQLite命令行(或DBeaver/VS Code SQLite插件),逐条执行:
-- 创建S表(供应商):注意把课本的"C(2)"改成"TEXT","UNIQUE"保留 CREATE TABLE S ( Sno TEXT UNIQUE, Sname TEXT, Status TEXT, City TEXT ); -- 创建P表(零件):同理,"C(2)"→"TEXT","INT"保持不变 CREATE TABLE P ( Pno TEXT UNIQUE, Pname TEXT, Color TEXT, Weight INT ); -- 创建J表(工程项目) CREATE TABLE J ( Jno TEXT UNIQUE, Jname TEXT, City TEXT ); -- 创建SPJ表(供应情况):注意这里没有UNIQUE,因为(Sno,Pno,Jno)联合主键 CREATE TABLE SPJ ( Sno TEXT, Pno TEXT, Jno TEXT, Qty INT, -- 联合主键,防止重复供应记录 PRIMARY KEY (Sno, Pno, Jno) );为什么用TEXT不用CHAR?因为现代数据库对变长字符串更友好,且避免CHAR(2)存'J1'时自动补空格导致匹配失败。这是课本没写的实战细节。
3.2 第二步:插入课本示例数据(最小可行集)
只插够验证第6题前两问的数据:
-- 插入S表数据(供应商) INSERT INTO S VALUES ('S1', '精益', '20', '天津'); INSERT INTO S VALUES ('S2', '盛锡', '10', '北京'); INSERT INTO S VALUES ('S3', '东方', '30', '上海'); -- 插入P表数据(零件) INSERT INTO P VALUES ('P1', '螺母', '红', 12); INSERT INTO P VALUES ('P2', '螺栓', '绿', 17); INSERT INTO P VALUES ('P3', '螺丝', '红', 14); -- 插入J表数据(工程) INSERT INTO J VALUES ('J1', '三建', '北京'); INSERT INTO J VALUES ('J2', '一汽', '长春'); INSERT INTO J VALUES ('J3', '船舶', '上海'); -- 插入SPJ表数据(供应关系) INSERT INTO SPJ VALUES ('S1', 'P1', 'J1', 200); INSERT INTO SPJ VALUES ('S1', 'P2', 'J1', 100); INSERT INTO SPJ VALUES ('S2', 'P1', 'J2', 300); INSERT INTO SPJ VALUES ('S2', 'P3', 'J3', 400); INSERT INTO SPJ VALUES ('S3', 'P1', 'J1', 500);关键点:
INSERT INTO SPJ VALUES(‘ S2’,‘ J6’,‘ P4’,200)这种课本答案里的空格(‘ S2’)是典型陷阱!务必删掉,否则WHERE 'S2' = ' S2'永远为假。
3.3 第三步:执行课本P42第6题第1问(最简单起点)
题目:“求供应工程J1零件的供应商号码SNO”
课本答案:SELECT DIST SNO FROM SPJ WHERE JNO='J1'
(注:课本笔误,DIST应为DISTINCT)
-- 执行查询(注意:DISTINCT不是DIST) SELECT DISTINCT SNO FROM SPJ WHERE JNO='J1';预期结果:
SNO --- S1 S3为什么是S1和S3?因为SPJ表里有两条JNO='J1'的记录:(S1,P1,J1)和(S3,P1,J1)。
DISTINCT去重,所以S1只出现一次。
3.4 第四步:升级挑战——课本第3问(带JOIN的查询)
题目:“求供应工程J1零件为红色的供应商号码SNO”
课本答案:SELECT SNO FROM SPJ,P WHERE JNO='J1' AND SPJ.PNO=P.PNO AND COLOR='红'
-- 执行(注意表别名让语句更清晰) SELECT DISTINCT SPJ.SNO FROM SPJ JOIN P ON SPJ.PNO = P.PNO WHERE SPJ.JNO = 'J1' AND P.Color = '红';预期结果:
SNO --- S1 S3解释:SPJ表里JNO='J1'的记录有(S1,P1,J1)和(S3,P1,J1);P表里Color='红'的有P1和P3;JOIN后,只有P1能匹配上,所以S1和S3都供应了红色零件P1。这就是“连接”的威力——它自动把分散在不同表里的信息缝合起来。
3.5 第五步:验证课本第5问(最难的“除运算”)
题目:“求至少用了供应商S1所供应的全部零件的工程号JNO”
课本答案分两步:
A.SELECT DISTINCT PNO FROM SPJ WHERE SNO='S1'→ 得到(S1供应的零件:P1,P2)
B.SELECT JNO FROM SPJ WHERE PNO='P1' AND JNO IN (SELECT JNO FROM SPJ WHERE PNO='P2')
我们直接执行B步(A步结果已知):
-- 找出既用了P1又用了P2的工程号 SELECT DISTINCT JNO FROM SPJ WHERE PNO = 'P1' AND JNO IN ( SELECT JNO FROM SPJ WHERE PNO = 'P2' );预期结果:
JNO --- J1为什么是J1?因为SPJ表里:J1用了P1(S1,P1,J1)和P2(S1,P2,J1);J2只用了P1(S2,P1,J2),没用P2;J3只用了P3。所以只有J1满足“S1供应的所有零件(P1,P2)它都用了”。
恭喜!你刚刚用真实SQL跑通了课本前三道核心查询。这不是死记硬背,而是亲手验证了“关系代数→SQL→真实数据”的完整链条。
4. 别再死磕“范式”:用一张表的演变,看懂1NF、2NF、3NF到底在解决什么问题
第六章的“函数依赖”“BCNF”“多值依赖”是新手最大心理障碍。课本定义密密麻麻,但它的出发点特别朴素:怎么设计一张表,让它不容易存错、改错、删错数据?我们用一个真实场景来演示。
4.1 从“一张大杂烩表”开始:原始需求与它的灾难
假设你要建一张“学生选课成绩表”,老板说:“就一张表,包含所有信息!” 你可能这样设计:
| 学号 | 姓名 | 系名 | 系主任 | 课程号 | 课程名 | 成绩 |
|---|---|---|---|---|---|---|
| 001 | 张三 | 计算机 | 王教授 | C001 | 数据库 | 85 |
| 001 | 张三 | 计算机 | 王教授 | C002 | 操作系统 | 92 |
| 002 | 李四 | 数学 | 陈教授 | C001 | 数据库 | 78 |
问题立刻爆发:
- 数据冗余:张三的系名、系主任重复存了2次;李四的系名、系主任也重复。如果王教授退休了,你得改2行;
- 更新异常:想把“计算机系”主任改成“李教授”,漏改一行,数据就自相矛盾;
- 删除异常:如果张三退学了,删掉他所有选课记录,系主任信息也跟着消失了;
- 插入异常:新成立一个“人工智能系”,还没招学生,你无法插入系信息(因为学号不能为空)。
这就是未规范化的表(0NF)——所有字段挤在一起,毫无结构。
4.2 拆成两张表:解决冗余,进入1NF和2NF
第一步,把“学生基本信息”和“选课成绩”分开:
学生表(Student):
| 学号 | 姓名 | 系名 |
|---|---|---|
| 001 | 张三 | 计算机 |
| 002 | 李四 | 数学 |
选课表(SC):
| 学号 | 课程号 | 成绩 |
|---|---|---|
| 001 | C001 | 85 |
| 001 | C002 | 92 |
| 002 | C001 | 78 |
现在满足1NF了吗?
课本定义:“每一个分量是不可再分的数据项”。是的——学号是原子值(不是“001,002”这种列表),成绩是数字,没有逗号分隔的多值。✅
但还有问题:系名→系主任的依赖没解决。
在Student表里,“系名”决定“系主任”(计算机系→王教授),但“系名”不是主键(学号才是),这就是部分函数依赖:非主属性(系主任)依赖于主键的一部分(系名),而不是整个主键(学号)。
解决方案:再拆!
- 系表(Department):| 系名 | 系主任 |
- 学生表(Student):| 学号 | 姓名 | 系名 |(系名变成外键)
此时Student表:主键是学号,所有非主属性(姓名、系名)都完全依赖于学号;系名作为外键,指向Department表。这就消除了部分依赖,达到2NF。
4.3 再拆一次:解决传递依赖,进入3NF
现在Student表里还有隐患:如果“系名”变了(比如“计算机系”改名“智能科学系”),你得改Student表里所有相关行。更好的方式是用“系号”作主键:
系表(Department):
| 系号 | 系名 | 系主任 |
|---|---|---|
| D01 | 计算机 | 王教授 |
| D02 | 数学 | 陈教授 |
学生表(Student):
| 学号 | 姓名 | 系号 |
|---|---|---|
| 001 | 张三 | D01 |
| 002 | 李四 | D02 |
为什么这是3NF?
课本定义:“不存在非主属性对码的传递函数依赖”。
- Student表主键是学号;
- 非主属性是姓名、系号;
- 姓名完全依赖学号(没问题);
- 系号完全依赖学号(没问题);
- 系号→系主任的依赖,在Department表里,不经过Student表传递。
所以,所有依赖都是“直接”的,没有“学号→系号→系主任”这种链条。✅
这就是3NF的核心:让每个表只负责一件事,且所有信息都直接由主键决定。不是炫技,是防错。
4.4 避坑:范式学习中最常见的3个误解
误解:“范式越高越好,必须做到BCNF”。
真相:BCNF会过度拆分,导致大量JOIN影响性能。实际项目中,3NF是黄金标准。课本P152第7题说“各关系模式都只有一个码……所以属于BCNF”,那是教学简化,真实业务表很少追求BCNF。误解:“1NF就是不能有重复列”。
真相:1NF的关键是“原子性”。比如“电话”字段存“1381234,1395678”就是违反1NF(逗号分隔多值);但存JSON字符串{"mobile":"138****1234","work":"010-12345678"},只要数据库把它当一个不可分割的字符串,就符合1NF。误解:“函数依赖X→Y,必须Y的值由X计算得出”。
真相:函数依赖是业务规则,不是数学公式。比如“身份证号→姓名”,不是用身份证号算出姓名,而是业务上规定:同一个身份证号,永远对应同一个人名。这是语义约束,不是算法。
5. 下一步学什么:从“抄答案”到“自己出题”,我的3步渐进路线
你已经能读懂课本答案、能建表、能跑通SPJ查询、能理解范式背后的防错逻辑。下一步不是去啃《数据库系统实现》,而是把这份答案集变成你的“肌肉记忆训练器”。我第一次学的时候,也是从这份答案起步,后来带新人发现:真正的入门门槛不在知识深度,而在“敢不敢把课本题当成真实需求来解”。以下是我亲测有效的3步法,每一步都对应一个具体动作:
5.1 第一步:把课本习题当“需求文档”,重写SQL(重点练“翻译能力”)
不要满足于看懂答案。拿出纸笔,把课本P42第6题当成产品经理给你的需求:
“老板要查:所有供应‘三建’工程的供应商,以及他们供应的零件颜色和重量。”
你的任务:
- ① 识别涉及哪些表?(SPJ、J、P、S)
- ② 识别过滤条件?(J.JNAME='三建')
- ③ 识别要查的字段?(S.SNO, P.Color, P.Weight)
- ④ 写出JOIN路径?(SPJ→J找工程,SPJ→P找零件)
- ⑤ 最后写出完整SQL(不看答案!)。
做完后,对照课本P59第4题第9问的答案SELECT S.SNO, P.Color, P.Weight FROM SPJ,J,P,S WHERE SPJ.JNO=J.JNO AND SPJ.PNO=P.PNO AND SPJ.SNO=S.SNO AND J.JNAME='三建'。
差距在哪?是少写了某个JOIN条件?还是字段名写错了表前缀?把这些错误记在本子上,下次同类题自动预警。
5.2 第二步:给答案“挑刺”,尝试优化(重点练“工程思维”)
课本答案是教学导向,不是性能导向。比如P59第4题第4问:
“求没有使用天津供应商生产的红色零件的工程号JNO”
课本答案:SELECT DISTINCT JNO FROM SPJ WHERE JNO NOT IN (SELECT JNO FROM SPJ,P,S WHERE S.CITY='天津' AND P.COLOR='红' AND S.SNO=SPJ.SNO AND P.PNO=SPJ.PNO)
这个NOT IN在大数据量下会很慢。你的任务:
- ① 用
LEFT JOIN + IS NULL重写(更高效):
SELECT DISTINCT SPJ.JNO FROM SPJ LEFT JOIN ( SELECT SPJ2.JNO FROM SPJ SPJ2 JOIN S ON SPJ2.SNO = S.SNO JOIN P ON SPJ2.PNO = P.PNO WHERE S.City = '天津' AND P.Color = '红' ) AS bad_jno ON SPJ.JNO = bad_jno.JNO WHERE bad_jno.JNO IS NULL;- ② 思考:为什么这个更快?因为
NOT IN遇到NULL会返回空结果,而LEFT JOIN逻辑更确定。
这不是炫技,是培养“数据量意识”——你写的SQL,未来可能跑在百万级数据上。
5.3 第三步:反向出题,给自己挖坑(重点练“设计能力”)
合上答案,打开空白文档,做这件事:
- ① 选一个现实场景(比如“图书馆借阅系统”);
- ② 设计3张表:Book(书号、书名、作者)、Reader(读者号、姓名、单位)、Borrow(读者号、书号、借期、还期);
- ③ 给自己出3道题:
- Q1:查所有“计算机类”书籍的借阅次数(需JOIN Book和Borrow);
- Q2:查从未借过书的读者(用
NOT EXISTS或LEFT JOIN); - Q3:查借阅过《数据库系统概论》且还期超过30天的读者(需日期计算)。
- ④ 写出答案,再用SQLite建表、插测试数据、验证。
从“解题者”变成“出题者”,你就真正入门了。因为出题要思考:数据怎么组织(范式)、查询怎么关联(JOIN)、边界怎么处理(NULL、空集)。这比刷100道题都管用。
从那以后我每次拿到新业务需求,都强制走一遍这个流程:先画ER图(哪怕就3个框),再想清楚主外键,最后才写第一行SQL。不是为了显得专业,而是因为跳过这三步写的代码,90%会在上线后半夜把你叫醒。希望帮到你。
本文还有配套的精品资源,点击获取