简介《数据库系统概念第七版》配套的“表结构及课后习题答案”资料包面向正在学习数据库原理的在校生、备考者及需要夯实SQL基础的自学者。内容围绕关系型数据库的表结构设计展开涵盖字段与数据类型定义、主键与外键约束、ER图到SQL语句的转换等核心知识点并结合学生表、课程表、成绩表等典型实例帮助读者理解规范化建模。包内共23个文件包含19个PDF格式的课后习题详解与4个SQL脚本DDL建表语句及小/大关系数据插入脚本压缩包约25.82MB可直接用于本地环境练习“建表—插数—查询—维护”的完整流程。习题答案覆盖多表连接、子查询、聚合函数等复杂SQL操作便于逐章对照自查。该资源已有5300余人学习下载适合作为教材研读之外的实操补充能有效提升数据库设计与查询能力。1. 数据库系统概念第七版这套表结构与习题答案解决自学者最卡的两件事《数据库系统概念第七版》这本书的难度从来不在概念本身而在书里的例子跑不起来。关系代数、范式分解、事务隔离光靠纸面推演很容易产生一种我懂了的错觉可真去写 SQL 的时候又处处碰壁。这个资源包里装的是两样东西贯穿全书 SQL 示例的 university 大学例库的表结构定义以及每章课后习题的参考解法。前者让你不用从零设计表也能动手建库后者让你写完 SQL 之后有地方核对对错。适合三类人按章节自学但缺少练习环境的人、备考数据库理论题需要反复验证 SQL 的人、以及带实验课但没时间自己造数据的助教。它不能替代教材但能让教材里每个例子都有地方落地。2. 把书里的 ER 模型拆开university 例库的表结构到底长什么样书里拿学生选课这个场景撑起了一整本书的 SQL 例子这套结构叫大学例库。表结构文件拿到手后先别急着导入把表拆开看懂后面调 SQL 和排错会顺很多。2.1 从实体关系到表department 和 instructor 的主键与外键设计先看最基础的两张表department系和 instructor教师。这两张表把实体和属性的映射讲得最清楚。常见的建表语句是CREATE TABLE department ( dept_name VARCHAR(20) PRIMARY KEY, building VARCHAR(15), budget NUMERIC(12,2) CHECK (budget 0) ); CREATE TABLE instructor ( ID VARCHAR(5) PRIMARY KEY, name VARCHAR(20) NOT NULL, dept_name VARCHAR(20), salary NUMERIC(8,2) CHECK (salary 0), FOREIGN KEY (dept_name) REFERENCES department(dept_name) );这里最值得玩味的是 department 用了 dept_name 做自然主键而不是另立一个 dept_id。第六版以前有人嫌字符串主键不稳定想换成 id但第七版仍然坚持自然键理由是例库要让读者直接看懂表结构少一层映射。代价是如果某天计算机系改名所有引用 dept_name 的表都要级联更新所以你在导入时看到一些表的外键会带 ON UPDATE CASCADE正是为了补偿这个设计。instructor 表里没有 building 和 budget 字段只有 dept_name 外键。这是范式化的直接体现楼宇信息和预算只属于 department教师表最多存一个系名更新系的信息不需要动 instructor。如果你在自建表时把 dept_name 替换成了 dept_id本质上就是给每张外键表加了一层查找SQL 里的 JOIN 也会变多书里后面讲自然连接和 using 的时候就和例库对不上了。salary 用 NUMERIC(8,2) 而不是 FLOAT是因为金额比较忌讳浮点误差。8 位有效数字可以表示到百万年薪两位小数存分。CHECK 约束是书上强调过的完整性约束第七版在 CHECK 上花的篇幅比旧版多你在导入时如果发现某个版本的表结构文件把 CHECK 去掉了那大概率是为了迁就老数据库的兼容性可以自行加回去。2.2 记录关系的连接表takes、teaches 为什么不能省有了实体表之后学生和课程之间的关系需要单独的表来承载。university 例库里最核心的连接表是 takes学生选课和 teaches教师授课结构大致如下CREATE TABLE takes ( ID VARCHAR(5), course_id VARCHAR(8), sec_id VARCHAR(8), semester VARCHAR(6), year NUMERIC(4,0), grade VARCHAR(2), PRIMARY KEY (ID, course_id, sec_id, semester, year), FOREIGN KEY (ID) REFERENCES student(ID), FOREIGN KEY (course_id, sec_id, semester, year) REFERENCES section(course_id, sec_id, semester, year) );复合主键五个字段意思是某个学生在某年某学期选了某门课的某个教学班这一条选课记录是唯一的。你可以注意到 grade 允许为空这不是设计疏漏——课程还没结束时成绩本就不存在留空正好表示进行中。很多初学者会把 grade 默认设成空字符串那样后续写 AVG(grade) 时会得到奇怪的聚合结果保持 NULL 才是符合 SQL 三值逻辑的做法。teaches 表结构类似只是把 ID 换成教师 ID。你可能会问直接给 course 表加一个 teacher_id 字段不是更简单答案是要考虑一门课可以被多个教师在不同学期教也可以一个教师一学期教多门课这是典型的多对多关系。如果不拆表course 表里只能塞一个逗号分隔的教师列表第一范式都过不了后面的所有 JOIN 练习也就没意义了。还有一张连接表值得注意prereq先修课程。它把 course 表自己和自己做多对多关联结构是 (course_id, prereq_id)两个字段都外键指向 course。这种自引用外键是书里专门会讲的写法遇到先修课的先修课这类递归查询时第七版给出的标准答案会用到 WITH RECURSIVE而这份表结构能不能支持递归查询取决于你选用的数据库版本。2.3 第七版例库的数据类型选择与时态字段第七版比旧版更贴近现代 SQL 标准数据类型的选用有几处值得留意。我把常见字段类型列一张表导入前对照检查字段含义类型选择理由ID 类编号VARCHAR(5) / VARCHAR(8)用定长字符串不用自增整数因为书里要演示自然键拼接金额NUMERIC(12,2) / NUMERIC(8,2)精确十进制运算避免浮点误差学期VARCHAR(6)存 Fall / Spring枚举值不用数字代号年份NUMERIC(4,0)演示中避免 DATE 类型带来的时区与格式化问题时间槽time_slot 表 time_slot_id 外键时间段单独成表不占 section 行内time_slot 单独成表是这一版例库的一个亮点。如果不拆section 表里每一条开课记录都要存 start_time 和 end_time遇到不同星期重复上课就要重复存好几行数据冗余严重。拆出 time_slot 之后一个时间槽可以被多个 section 复用也更方便做第七章的时态数据扩展。另外要提醒的是第七版教材正文里用的 SQL 是标准 SQL 风格比如 FETCH FIRST、WITH RECURSIVE 这类写法。但表结构文件为了兼容不同数据库通常会被导成 MySQL 方言。你在 MySQL 里导入后跑书上的某些标准 SQL 会发现语法不支持这不代表表结构出错而是方言差异到第五章我会专门讲怎么排查。3. 把表结构变成能查的库三种导入路径与最小操作命令拿到表结构文件之后第一个问题永远是导入哪个数据库。我的建议是别一上来就纠结选型MySQL、PostgreSQL、SQLite 各有各的适用场景跟着你的习题环境走就行。3.1 用 MySQL 导入SOURCE 命令与字符集设置如果你手头已经有 MySQL导入最直接。先把建表语句文件放到一个路径里然后mysql -u root -p进入客户端之后CREATE DATABASE university DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; USE university; SOURCE /path/to/university_ddl.sql;SOURCE 是 mysql 客户端的专用命令它逐行执行文件里的 SQL。路径这里我建议用正斜杠Windows 用户也要写 C:/data/university_ddl.sql 而不是 C:\data...反斜杠会被转义解析出歧义。utf8mb4 是必须的。MySQL 里直接写 utf8 实际上建出来的是 utf8mb3只支持基本多语言平面中文够用但遇到 emoji、某些生僻字会报错或写入失败。配套的排序规则 utf8mb4_0900_ai_ci 是 MySQL 8.0 的默认值如果你用的是 5.7这里要换成 utf8mb4_general_ci。如果建库的时候没指定字符集后面发现中文乱码补救方式是把库和表整体转换ALTER DATABASE university CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; ALTER TABLE takes CONVERT TO CHARACTER SET utf8mb4;导入完成先做两件验证不要急着做题SHOW TABLES; SELECT COUNT(*) FROM takes;SHOW TABLES 确认十几张表都建出来了COUNT 查询确认数据文件也成功导入。如果 COUNT 返回 0说明你导入的只是表结构文件数据是另一份需要继续 SOURCE 数据文件。SOURCE 报错时错误信息不会直接告诉你是哪一行我一般配合 \T log.txt 把会话输出记录到文件里再对着日志定位不然一两百条语句刷过去眼睛根本追不上。3.2 用 PostgreSQL 导入psql 的 \i 与方言差异PostgreSQL 在 SQL 标准符合度上公认比 MySQL 高读第七版教材做验证时体验会好很多尤其是第七章讲窗口函数和递归查询的地方。导入命令是createdb -U postgres --encodingUTF8 university psql -U postgres -d university -f /path/to/university_ddl.sqlpsql 的 -f 参数直接执行 SQL 文件等价于进入 psql 后敲 \i /path/to/...。PG 对标准 SQL 的保留字更严格比如 user、year 这种字段名在 MySQL 里能直接建在 PG 里会报语法错误如果表结构文件里没处理这些你需要在建表语句里加双引号。反过来文件里如果用了 MySQL 的 AUTO_INCREMENTPG 会拒绝执行要改成 GENERATED BY DEFAULT AS IDENTITY。导入过程中如果错误太多建议在 psql 里先设置\set ON_ERROR_STOP on这样遇到第一条错误就停下来不会让后面的错误把终端刷花。导入成功后运行 ANALYZE 更新统计信息否则优化器可能给出奇怪的长计划。ANALYZE;验证命令换成\dt \d instructor\dt 列出当前库所有表\d 表名看字段和约束。PG 的 \d 输出比 MySQL 的 DESC 更详细外键和索引都直接列出来排查问题方便一些。提示PG 对索引和约束的命令采用不同命名空间如果 \d 看不到某个索引用 \di 单独列索引。3.3 只想快速验证习题SQLite 内存库怎么搭如果只是想把习题答案跑一遍验证语法不想装任何服务SQLite 是最省事的路径。把表结构文件转成 SQLite 能认的版本后sqlite3 university.db ddl_sqlite.sql sqlite3 university.db进入后直接写 SQL 验证答案。更推荐的做法是连文件都不建用内存库sqlite3 :memory: ddl_sqlite.sql内存库每次启动都是空库数据不落盘适合反复折腾场景——你改坏了数据重启命令就回到初始状态不需要重建库。这一点在练习 DELETE、UPDATE 这类破坏性语句时特别好用。不过 SQLite 有两个前置开关要留意。第一个是外键默认关闭需要手动打开PRAGMA foreign_keys ON;第二个是类型亲和性SQLite 没有真正的 NUMERIC 严格类型它允许 NUMERIC(8,2) 字段里存整数也允许存字符串。做题时如果遇到隐式类型转换导致的怪结果先检查表结构文件在转换到 SQLite 时是否保留了 CHECK 约束和 NOT NULL。窗口函数要在 SQLite 3.25 以上才可用WITH RECURSIVE 新版本没问题FETCH FIRST 这类标准语法则得手动改成 LIMIT。如果你跑课后习题卡在语法上先确认 sqlite3 --version 再决定要不要换库这条路本身就是为了省事别为了环境跟语法死磕。4. 课后习题答案的正确用法对照验证、定位错因、反推考点表结构落地之后习题答案才是重头戏。但直接看答案是最浪费的用法三道题之后你会发现自己什么都没学会。我的习惯是答案只作为验证工具不当作教材。4.1 答案对照的三层用法跑通、比结果、比执行计划第一层是把答案 SQL 拿到库里跑通确认你的数据库环境没问题。这一步看起来简单实际能筛掉三分之一的翻车场景——建表不完整、外键错位、数据没导入都在这个阶段暴露。第二层是你先写完自己的 SQL再和答案 SQL 的结果集比对。对比不是肉眼瞄一眼而是用集合运算判断是否等价-- 假设 my_answer 是你的结果ref_answer 是答案结果 (SELECT * FROM my_answer EXCEPT SELECT * FROM ref_answer) UNION ALL (SELECT * FROM ref_answer EXCEPT SELECT * FROM my_answer);如果这个查询返回空集说明两个查询结果集合完全相同你的写法与答案在数据层面等价。注意我对 UNION ALL 和 EXCEPT 的位置两个方向都查是因为只查一边只能证明你的结果包含在答案里不能证明反方向。这个技巧用来处理查询结果以多列返回、顺序不定的场景特别有用。第三层是比执行计划。对于同一道题你的写法和答案写法结果一样但执行计划可能差出一个数量级。用 EXPLAIN 看一遍扫描方式比盲猜为什么答案这么写高效得多。EXPLAIN SELECT dept_name, name, salary FROM instructor i WHERE salary ( SELECT MAX(salary) FROM instructor WHERE dept_name i.dept_name );执行计划里如果能看到 DEPENDENT SUBQUERY 标记说明这个子查询对每一行外层数据都要重新执行一次数据量一大性能就会崩。这就是相关子查询的代价你会直观理解为什么后面要引入派生表和窗口函数。4.2 一道聚合查询题的验证实例从结果集到排错拿一道典型课后题举例查找每个系工资最高的教师。参考答案常见写法是SELECT dept_name, name, salary FROM instructor i WHERE salary ( SELECT MAX(salary) FROM instructor WHERE dept_name i.dept_name );你的第一直觉可能是 GROUP BYSELECT dept_name, MAX(salary) FROM instructor GROUP BY dept_name;两种写法结果集不同GROUP BY 版本只返回系名和最高工资拿不到是谁相关子查询版本能同时返回教师名。这道题的考点本质上就是聚合后取明细行的能力。第七版还会再升级为窗口函数写法SELECT dept_name, name, salary FROM ( SELECT dept_name, name, salary, RANK() OVER (PARTITION BY dept_name ORDER BY salary DESC) AS rk FROM instructor ) AS t WHERE rk 1;窗口函数版本不仅处理了每个系最高工资是谁还天然支持并列第一不会因为 WHERE salary MAX 这种写法在多行并列时产生歧义。验证时用 4.1 的集合比较方法你会发现 GROUP BY 版本与答案列数都不一样直接 EXCEPT 会报错所以要先用列对齐再比。这个报错本身也是学习点——你至少会意识到 SELECT 的列清单决定了后续所有操作包括集合运算。如果答案跑出来是空结果最可能的原因不是 SQL 错而是 instructor 表里的数据有 dept_name 为 NULL 的教师。相关子查询遇到 NULL 外键时WHERE 条件恒为 UNKNOWN行被过滤。这种坑在书里讲三值逻辑时专门提过但只有你在真实数据上撞一次才会记得牢。4.3 当答案和你的结果不一样时先查数据而不是查 SQL我踩过最多次的坑是结果集对不上第一反应就去改 SQL改了三轮才发现是数据的问题。排查顺序必须是数据 → 字段语义 → SQL。数据层面先确认导入的数据是否完整比如 student 表如果只有十几条记录那后面所有涉及 JOIN 的题都跑不出书上预期的量级。字段语义层面要确认年份字段是 NUMERIC 还是 VARCHAR——如果是字符串2010 和 2010 在 WHERE 比较时会发生隐式转换结果可能不报错但错误。最后才轮到 SQL 本身。还有一个容易被忽略的排查项多表 JOIN 时外键有没有 NULL。真实世界的数据不会像书里那么干净如果答案里用了 INNER JOIN 而你的数据有 NULL 外键结果集会差出记录你以为 SQL 写错了实际上换成 LEFT JOIN 就对了。把这三个层面查完才轮到怀疑答案本身。5. 导入与使用避坑编码、外键顺序、版本差异的排查记录下面这几条都是实操里反复出现的坑每次遇到都有人说是玄学其实每条都有确定原因按现象 → 原因 → 解决写清楚你可以直接对照排查。5.1 解压后文件名乱码GBK 与 UTF-8 的恩怨现象在 Windows 上解压正常文件拷到 Linux 服务器后解压出来的文件名一堆乱码但打开文件内容却正常。原因压缩包在 Windows 下生成时文件名用 GBK 编码Linux 的 unzip 默认按 UTF-8 解码字符错位就成了乱码。文件内容不乱码是因为内容本身被正确编码和文件名是两条线。解决不要用 unzip改用 7z 并指定编码参数7z x university.rar -o./db -mcp936936 是 GBK 的代码页编号。如果你已经解压成乱码了可以用一段小脚本批量还原import os for name in os.listdir(.): if / in name or \x in name: # 乱码文件的特征按实际判断 fixed name.encode(latin-1, errorsignore).decode(gbk, errorsignore) os.rename(name, fixed)注意批量改名是不可逆操作编码映射猜错会把文件名改得更乱。执行前先备份一份或者先用 ls -b 查看文件名实际字节确认乱码字符范围再动手。脚本里的 encode/decode 链是赌编码映射不一定能 100% 还原。更稳的方案是回到 Windows 上重新解压一次或者直接选择 UTF-8 编码的压缩包版本。这属于前置预防比事后改名省事太多。5.2 外键约束导致建表顺序翻车现象SOURCE 执行到某一条建表语句时报错提示 cannot add foreign key constraint但单独看这条语句并没有问题。原因建表文件里表与表之间存在外键依赖被引用的父表还没建出来子表的外键自然挂靠不上。常见于 takes 表引用了 section 表而 section 表在文件里排在后面。解决MySQL 下可以在导入前临时关掉外键检查SET FOREIGN_KEY_CHECKS 0; SOURCE /path/to/university_ddl.sql; SET FOREIGN_KEY_CHECKS 1;这条开关可以保证建表顺序无所谓最后所有表都建立后外键关系依然有效。PostgreSQL 没有这个开关替代方案是按依赖顺序手动切分执行先 department、course再 instructor、section最后 takes、teaches。或者用 psql 的 \ir 一条条控制执行顺序虽然麻烦些但你能顺带把表之间的引用关系理清楚。5.3 MySQL 与书中的 SQL 方言差异LIMIT、日期、字符串函数现象从书上抄下来的标准 SQL 在 MySQL 里报语法错误比如SELECT name FROM instructor ORDER BY salary DESC FETCH FIRST 1 ROWS ONLY;原因MySQL 一直不支持 SQL 标准的 FETCH FIRST它的方言是 LIMIT。第七版教材正文用标准 SQL习题答案经常混着写看答案之前先确认它按哪套标准写的。解决MySQL 里改成SELECT name FROM instructor ORDER BY salary DESC LIMIT 1;PostgreSQL 两种写法都接受所以如果你发现同一道答案在 PG 里能跑、MySQL 里不能跑先往方言差异想。日期函数也是重灾区MySQL 用 YEAR(date_col) 提取年份PG 用 EXTRACT(YEAR FROM date_col)字符串截断 MySQL 的 SUBSTRING 起点是 1很多报表语言却是 0 起点。还有一类隐藏坑是 LIKE 的大小写规则MySQL 默认不区分大小写PG 区分同一道模糊查询题在两个库里结果集不一样不代表你 SQL 写错了是排序规则不同。这类差异不致命但会让你浪费半小时排查一个不会报错的错误结果。5.4 习题答案数据对不上先核对种子数据现象你的 SQL 跑出来的结果和答案给出的数值不一样比如统计课程数量答案写着 68你查出 37。原因表结构文件可能配套的种子数据与书上印刷的数据版本不同。书上的数据是作者造出来配合讲解的每一版印刷都可能微调你手上的数据文件版本和书不一致结果自然对不上。解决以你实际导入的数据为准。答案的价值在解题思路和 SQL 结构不在数字本身。如果你确实想让结果和书上完全一致需要找到与第七版同步的那一份种子数据文件导入后做一次 COUNT(*)确认关键表行数。用习题答案验证 SQL 逻辑时优先比集合是否为空而不是比具体数字。注意如果自己重新生成数据建议用固定随机种子比如 PG 里的 SET seed 42或者用 generate_series 生成确定性序列。这样每次跑 SQL 结果一致方便反复调优不掉进随机数据的坑。6. 把学过的题变成自己的练习场索引优化、触发器与时态表扩展环境建好、答案也对照过一轮之后这套资源的价值才真正开始。建议你做两件事在例库上做性能实验以及把答案 SQL 变成可复用的检查工具。6.1 用习题表做索引前后对比实验EXPLAIN SELECT * FROM takes WHERE course_id CS-101;先不建索引执行一次看扫描方式大概率是全表扫描。再建CREATE INDEX idx_takes_course ON takes(course_id, sec_id);重新执行 EXPLAIN观察访问类型从全表扫描变成索引查找。这个对比在只有几百条数据的例库上也能看出区别原理是索引把查询的扫描单位从全表行数降到了索引树深度。第七版讲 B 树索引时不会把话说破但你在自己的例库上亲手看到执行计划变化比背十遍索引结构都直观。6.2 把答案变成自动化检查工具视图与集合运算把每章习题答案封装成视图然后写一个通用的等价性检查。假设参考答案视图叫 ref_q7你自己的查询视图叫 my_q7SELECT CASE WHEN NOT EXISTS ( (SELECT * FROM ref_q7 EXCEPT SELECT * FROM my_q7) UNION ALL (SELECT * FROM my_q7 EXCEPT SELECT * FROM ref_q7) ) THEN 一致 ELSE 不一致 END AS check_result;以后每改造一次自己的 SQL就重跑一遍这个检查比对着屏幕肉眼比结果靠谱。如果想把检查再做深一层可以在此基础上加触发器当参考答案视图的底层数据变动时自动重算并记录检查结果那就把习题验证变成了一套小型的回归测试系统。第七版还专门讲了时态数据库你可以拿 section 表做扩展实验给开课记录加上 valid_start 和 valid_end 两个有效时间列然后写查询找出某个时间点正在上的课程。这类题在普通练习里遇不到但在例库上改造起来成本很低值得自己动手加一版。这也是我最后想说的一点这份资源和答案的价值不在背下来而在把它拆成你能反复折腾的实验环境。我最初直接抄答案效果很差后来每道题都先自己写、再用上面的集合比较法验证、最后把答案封装成视图留着才真正把书里的逻辑吃透。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?