简介:这份《头歌MySQL数据库实训答案有目录.pdf》面向正在学习MySQL基础、准备完成头歌平台数据库实训的学生与初学者,针对建库建表、约束设置、表结构维护及数据操作等关卡提供可对照的参考语句与答案整理。包内共1个PDF文件,压缩包约433KB,体量轻便,便于在电脑或移动端查阅;正文按实训模块编排并附目录,可较快定位到对应关卡。内容覆盖CREATE DATABASE与CREATE TABLE、主键/外键/唯一/默认值/自增约束,以及DESCRIBE查看表结构、ALTER TABLE改名改字段、增删字段、调整字段位置、删除外键,并延伸到插入与更新数据等常见操作,适合课程实验、课后作业与期末复习时逐关核对。已有18407人学习,说明其在同类实训答案中具有一定参考度。读者可借目录核对SQL写法,理解约束与表结构变更的执行效果,并用可复制的语句完成练习和排错复盘。
1. 带目录的 MySQL 数据库实训答案,先确认它到底是“答案集”还是“可执行脚本”
判题式实训平台上,一关只回你一句“通过”或“未通过”,中间过程全当黑匣子。这份 MySQL 数据库实训答案 PDF 带目录,把几十个关卡按模块排好了:从建库建表、五类约束,到 ALTER 改表、单表查询、连接查询、子查询、聚合函数、视图、索引、存储过程和自定义函数,一条一条能对着抄。它的真实价值不是交作业,而是一份最小可运行的 SQL 样本库——每段拖进本地 mysql 客户端跑一遍,看表结构怎么变、结果集长什么样,比在判题框里点十次提交学到的多。适合刚上手 MySQL 语法的在校生、要带实验课的助教,以及隔几个月就得重写一遍 DDL 的后端。但它毕竟是 PDF,不是.sql文件,复制粘贴沿途全是坑,后面几章就是逐个拆开。
2. 从 mysql -uroot -p 到五类约束:把建库建表写到判题器认账
建表是整个实训里最容易“一次错、后面全崩”的环节。DDL 写错一个括号,后面所有 DML 都跑不起来;约束少写一个,判题器比对表结构时直接判负。这一章先把连接串、建库建表的三层结构、五类约束的写法拆清楚,再讲怎么把 PDF 里的段落安全地搬进客户端。
2.1 连接串里的四个参数,每一个都能让你连不上
答案里几乎每关开头都有一行mysql -uroot -p123123 -h127.0.0.1,这是判题环境给你的现成连接方式。真在自己机器上跑,我一般改成下面这样,密码不进 shell 历史,字符集提前对齐,省掉后面一堆乱码排查。
# 答案里的写法:-p 和密码之间不能有空格,有空格会被当成库名 mysql -uroot -p123123 -h127.0.0.1 # 我习惯的写法:-p 单独出现,回车后交互式输入密码 mysql -h 127.0.0.1 -P 3306 -u root -p # 直接指定库并锁定字符集,避免中文条件和结果乱码 mysql -h 127.0.0.1 -P 3306 -u root -p --default-character-set=utf8mb4 Company四个参数各管一件事:-u是账号,-p是密码开关(紧贴密码等价于明文传参),-h是主机地址,-P是大写端口(小写-p已经被密码占了)。判题环境常见组合就是127.0.0.1:3306加一个弱口令账号,本地复现时照着填即可。--default-character-set建议显式写utf8mb4,因为答案里有DEFAULT CHARSET=utf8这种写法,而 utf8 在 MySQL 里是 utf8mb3 的别名,存 emoji 或部分生僻字会被截断,本地跑和判题环境不一致时,报错信息会非常迷惑。
提示:连接成功后第一件事永远是
SELECT DATABASE();,确认自己在哪个库。答案里USE Company;和USE School;交替出现,切错库的表现是“表不存在”,而不是语法错误。
2.2 建表语句的三层结构:列定义、约束、表选项
答案第 2 关那段建表是最朴素的版本,正好拿来当骨架看:
CREATE DATABASE IF NOT EXISTS MyDb DEFAULT CHARACTER SET utf8mb4; USE MyDb; CREATE TABLE t_emp ( id INT, -- 列名 类型 name VARCHAR(32), -- 变长字符串,括号里是最大字符数 deptId INT, salary FLOAT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;一条CREATE TABLE可以切成三层:第一层是列定义,列名 数据类型(长度);第二层是约束,主键、外键、唯一、非空、默认值;第三层是表选项,存储引擎和字符集。三层分开看,改起来就不会漏。
类型选择上有两个实际边界值得记住。VARCHAR(32)的 32 是字符数不是字节数,utf8mb4 下最多占 128 字节,所以别拿它当“32 字节”算长度。FLOAT存金额会出现2300.0000001这种尾数,真做业务表我会用DECIMAL(10,2);答案里用FLOAT是因为题目只要求“能存下工资”,判题比对的是结果集数值而不是类型精度,但你本地复现时如果拿 FLOAT 做等值比较,就会翻车。
2.3 主键、复合主键、外键、唯一、默认值各写一遍
主键的本质是“唯一 + 非空 + 自带索引”,答案里给了单列和复合两种:
-- 单列主键:userId 唯一且非空 CREATE TABLE t_user1 ( userId INT PRIMARY KEY, name VARCHAR(32), password VARCHAR(11), phone VARCHAR(11), email VARCHAR(32) ); -- 复合主键:两列组合唯一,单独一列可以重复 CREATE TABLE t_user2 ( name VARCHAR(32), phone VARCHAR(11), email VARCHAR(32), PRIMARY KEY (name, phone) );复合主键的判题点是“组合唯一”,不是“每列唯一”。('张三','138')和('张三','139')可以同时存在,但('张三','138')插两次就报 1062。写复合主键时列顺序有意义,因为主键本身是一棵索引树,(name, phone)和(phone, name)在查询时的最左前缀匹配能力完全不同——这一点在索引那一章会再遇到。
外键是最容易写崩的一类,因为它牵扯建表顺序:
-- 父表必须先建,被引用的列必须有索引(主键自带) CREATE TABLE t_class ( id INT PRIMARY KEY, name VARCHAR(22) ); CREATE TABLE t_student ( id INT PRIMARY KEY, name VARCHAR(22), classId INT, CONSTRAINT fk_stu_class1 FOREIGN KEY (classId) REFERENCES t_class(id) );CONSTRAINT fk_stu_class1这半个句子是给约束起名字,名字看着多余,实际上是你后面DROP FOREIGN KEY时唯一能点名的东西——不起名 MySQL 会自动生成一个t_student_ibfk_1,你就得先去查。外键还隐含两个限制:子表classId的值必须在父表id里存在,父表被引用的行不能随便删。本地做实验时这两条经常互相打架,要么先删子表数据,要么临时SET FOREIGN_KEY_CHECKS=0。
剩下三类约束挤在一条语句里:
CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, -- 自增,依赖主键或唯一索引 username VARCHAR(32) NOT NULL UNIQUE, -- 非空 + 唯一,UNIQUE 会自动建索引 sex VARCHAR(4) DEFAULT '男' -- 默认值,只在“不提供该列”时生效 ) DEFAULT CHARSET=utf8;AUTO_INCREMENT必须挂在主键或唯一索引上,否则建表直接报 1075。UNIQUE和PRIMARY KEY的区别只在“允不允许 NULL”和“一张表能有几个”:主键只能一个且非空,唯一索引可以有多个且允许一个 NULL。DEFAULT的触发条件很窄——INSERT语句里完全不写这一列才会用默认值,显式写NULL是不会被替换成'男'的,这是新手最常误判的一条。
2.4 有目录的 PDF 怎么用:按关卡切块,别整篇复制
这份答案带目录,正确的用法是把它当索引而不是当脚本。我的做法是:按目录把每一关切成独立的一段,一段一个.sql文件,文件名带关卡编号,比如03-02-alter-column.sql。切块的好处是失败时能定位到具体哪一关,而不是对着一整篇几百行的文本找问题。
第二件事是每段开头强制补三行:USE 目标库;、DROP TABLE IF EXISTS 目标表;、建表语句。判题器每一关给的是一份干净的初始快照,你在本地却没有这个机制,上一关改过的表名、删过的字段会一路带下去。第 5 章有一条踩坑记录专门讲这个。
第三件事是执行前先DESCRIBE。答案里很多关的判题点就是表结构本身——字段顺序、类型长度、是否可空、键的类型,光看结果集是看不出来的。养成“先看结构再看数据”的顺序,能省掉一半返工。
3. ALTER TABLE 的六个动作:改名、改类型、挪位置、删外键
表结构一旦建好,后续所有变更都压在ALTER TABLE上。这一章的六个动作是实训里反复出现的:改表名、改列名、改类型、加列、删列、改列位置,外加一个删外键。每个动作在 MySQL 里都有它的脾气,尤其是“改位置时必须重写类型”这条,翻车率极高。
3.1 RENAME、CHANGE、MODIFY 三者的边界
这三个词长得像,职责完全不同:
USE Company; -- 改表名:RENAME 后面直接跟新表名,加 TO 更规范 ALTER TABLE tb_emp RENAME TO jd_emp; SHOW TABLES; DESCRIBE jd_emp; -- 改列名 + 改类型:CHANGE 必须写“旧列名 新列名 类型” ALTER TABLE tb_emp CHANGE Id prod_id INT(11); -- 只改类型:MODIFY 后面只跟“列名 新类型” ALTER TABLE tb_emp MODIFY Name VARCHAR(30); DESCRIBE tb_emp;RENAME只管表名,不动列。CHANGE是“重命名 + 重定义”二合一,语法要求写全三段——旧名、新名、类型,少写类型直接 1064。MODIFY只能改类型和属性,不能改名,想改名必须走CHANGE。
这里有个隐蔽的坑:这三条语句在答案里分布在不同关卡,而关卡之间环境是重置的。你在本地按顺序连着跑,第一句把tb_emp改成了jd_emp,第二句再ALTER TABLE tb_emp就会报 1146 表不存在。不是语法错,是你把上一关的状态带进了下一关。
INT(11)里的 11 也不是“长度限制”,MySQL 8.0.17 之后整型的显示宽度已经被标记为废弃,写INT和INT(11)存储完全一样。判题器如果比对SHOW CREATE TABLE的输出,那还是老老实实按答案写;如果只比对数据,写不写括号无所谓。
3.2 FIRST 与 AFTER:字段顺序也是判题点
加列、删列、挪位置这三件事经常一起考:
USE Company; -- 加列并指定插入位置:AFTER Name 表示排在 Name 后面 ALTER TABLE tb_emp ADD Country VARCHAR(20) AFTER Name; -- 删列 ALTER TABLE tb_emp DROP Salary; -- 改列位置到最前:MODIFY 改位置时必须重复写类型 ALTER TABLE tb_emp MODIFY Name VARCHAR(25) FIRST; -- 改列位置到指定列之后 ALTER TABLE tb_emp MODIFY DeptId INT(11) AFTER Salary; DESCRIBE tb_emp;ADD不带位置参数时默认追加到最后一列,AFTER 列名插到指定列后面,FIRST顶到最前。DROP会连同这一列上的索引、默认值、注释一起删掉,删之前最好SHOW CREATE TABLE留个底。
最容易翻车的是MODIFY ... FIRST和MODIFY ... AFTER。MySQL 的MODIFY语法要求必须带完整类型定义,只写MODIFY Name FIRST会直接语法错误。我见过不少人凭直觉写成ALTER TABLE t MODIFY COLUMN Name FIRST,然后对着 1064 发呆——这不是 MySQL 的怪癖,是它把“改位置”归到了“重定义列”里,重定义就得把类型再写一遍,哪怕一个字符都没变。
另外,DESCRIBE输出的字段顺序就是物理顺序,判题器如果比对DESC结果,位置写错一样判负。所以每改一次位置,我都习惯跟一条DESCRIBE确认。
3.3 删外键前先用 SHOW CREATE TABLE 拿到约束名
外键约束的删除语句和别的都不一样,它认的是约束名不是列名:
-- 先看清楚约束叫什么,\G 让结果竖排,长语句好读 SHOW CREATE TABLE tb_emp\G -- 删外键:DROP FOREIGN KEY 后面跟约束名 ALTER TABLE tb_emp DROP FOREIGN KEY emp_dept; -- 再确认一次 SHOW CREATE TABLE tb_emp\GDROP FOREIGN KEY emp_dept里的emp_dept是建表时CONSTRAINT emp_dept FOREIGN KEY ...起的名字,不是列名,也不是索引名。如果你建表时没起名,就得先从SHOW CREATE TABLE的输出里把自动生成的名字(形如tb_emp_ibfk_1)抄出来。
还有一个连带问题:删外键不会自动删索引。MySQL 在建外键时会顺手给子表那一列建一个普通索引,DROP FOREIGN KEY之后这个索引还在,SHOW CREATE TABLE里能看到KEY emp_dept (DeptId)。要让表结构彻底干净,还得补一条ALTER TABLE tb_emp DROP INDEX emp_dept;。判题器如果只校验外键消失,不补也行;但如果它比对完整的建表语句,这一步就不能省。
3.4 插改删三件套:多值 INSERT、带 WHERE 的 UPDATE、有范围的 DELETE
DML 看着简单,判题点全在“影响了哪些行”上:
USE Company; -- 批量插入:一条语句塞多行,比三条单行 INSERT 快得多 INSERT INTO tb_emp (Id, Name, DeptId, Salary) VALUES (1, 'Nancy', 301, 2300.00), (2, 'Tod', 303, 5600.00), (3, 'Carly', 301, 3200.00); -- 更新:WHERE 锁行,漏了就是全表改 UPDATE tb_emp SET Name = 'Tracy', DeptId = 302, Salary = 4300.00 WHERE Id = 3; -- 删除:按条件删,不是清空表 DELETE FROM tb_emp WHERE Salary > 3000; SELECT * FROM tb_emp;批量INSERT的括号组顺序无所谓,但列名列表和值列表必须一一对应,少一个值就会报 1136“列数与值数不匹配”。UPDATE的SET子句是从左到右求值的,SET Salary = Salary + 100, DeptId = DeptId + 1这种写法里,后面引用的是更新前的值,不会串。DELETE和TRUNCATE是两回事:DELETE逐行删、写事务日志、能回滚、不重置自增;TRUNCATE是重建表、不可回滚、自增归零,而且不能带WHERE。
金额字段要注意精度。答案里写4300.00,如果列类型是FLOAT,实际存进去可能是4300,SELECT出来也显示4300。判题器做数值比较时通常没问题,但如果它做字符串比较,4300和4300.00就不相等了。稳妥做法是建表时就用DECIMAL(10,2),让显示和存储都带两位。
4. 查询主线:条件族、排序分页、分组聚合到连接子查询
查询是整份答案里占比最大的部分,从最简单的SELECT Name, Salary FROM tb_emp一路排到“体育馆的人流量”这种三表自连接。这一章按难度铺开:条件族、排序与分页、分组聚合、连接查询、子查询与复杂题。每一层都会给一段能直接跑的代码,并说明判题器最可能卡你的那个点。
4.1 WHERE 条件族:IN、BETWEEN、LIKE、IS NULL、DISTINCT
USE Company; -- 基础投影:只取需要的列,比 SELECT * 少一次全列读取 SELECT Name, Salary FROM tb_emp; -- IN 与 NOT IN SELECT Name, Salary FROM tb_emp WHERE Id NOT IN (1); -- BETWEEN AND:闭区间,两端都包含 SELECT Name, Salary FROM tb_emp WHERE Salary BETWEEN 3000 AND 5000; -- LIKE:% 匹配任意长度,_ 匹配单个字符 SELECT Name, Salary FROM tb_emp WHERE Name LIKE 'C%'; -- 空值判断:不能用 = NULL SELECT * FROM tb_emp WHERE DeptId IS NULL; -- 去重:作用于整行组合,不是单列 SELECT DISTINCT Name FROM tb_emp;NOT IN有一个经典陷阱:如果括号里是一个子查询且子查询返回了 NULL,整个NOT IN会返回空集。原因是对 NULL 做任何比较结果都是 UNKNOWN,x NOT IN (1, NULL)等价于x <> 1 AND x <> NULL,后半段永远是 UNKNOWN。真要安全,用NOT EXISTS或者给子查询加WHERE col IS NOT NULL。
BETWEEN 3000 AND 5000是闭区间,等于>= 3000 AND <= 5000,日期范围查询时这点特别容易错——BETWEEN '2024-01-01' AND '2024-01-31'会漏掉 1 月 31 日带时分秒的数据。
LIKE 'C%'里前置通配符才毁索引,'%C'这种写法会让 B+ 树退化成全表扫描;'C%'是可以用索引的。DISTINCT Name去重的是“选取出来的整行”,如果你写SELECT DISTINCT Name, Salary,去重的是这两个字段的组合,不是只对 Name 去重。
4.2 ORDER BY 与 LIMIT:第 2 名到第 5 名到底是 1,4 还是 2,4
USE School; -- 单表排序:默认升序,DESC 降序 SELECT * FROM tb_score WHERE class_id = 1 ORDER BY score DESC; -- 分页:LIMIT offset, count -- “第 2 名到第 5 名” = 跳过第 1 条,取 4 条 SELECT * FROM tb_score ORDER BY score DESC LIMIT 1, 4;LIMIT的两种写法:LIMIT n等价于LIMIT 0, n,取前 n 条;LIMIT offset, count跳过 offset 条再取 count 条。offset 从 0 开始,所以“第 2 名到第 5 名”是LIMIT 1, 4不是LIMIT 2, 4。这个差一位的错误在实训里非常常见,因为判题器返回的结果集少一行多一行都会判负。
排序还有一个隐式行为:如果有并列值,MySQL 不保证并列行之间的相对顺序。想稳定复现,得补第二排序键,ORDER BY score DESC, id ASC。判题器如果只比对内容不比对顺序,那无所谓;如果它逐行比对,缺第二排序键就可能出现“同样的语句两次跑出不同结果”的玄学现场。
深分页是另一个话题。LIMIT 100000, 20的执行方式是扫描前 100020 行然后丢掉前 100000 行,越翻越慢。答案在“分页和索引”那一关给了一种游标式改写,用上一页的最大 id 做锚点,第 6 章会具体展开。
4.3 GROUP BY 与 HAVING:先筛行还是先分组
USE School; -- 多列分组:年级 + 性别,统计各组合人数 SELECT gradeId, sex, COUNT(*) FROM student WHERE gradeId IN (2, 3, 4) GROUP BY gradeId, sex; -- HAVING 过滤分组结果:至少两门课 90 分以上 SELECT sno, COUNT(*) FROM tb_grade WHERE score >= 90 GROUP BY sno HAVING COUNT(pno) >= 2;执行顺序是FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。WHERE在分组之前过滤行,HAVING在分组之后过滤组,两者不能互换——HAVING score >= 90在单行上根本没意义,因为分组后score已经被聚合掉了。
聚合函数就五个:COUNT、SUM、AVG、MAX、MIN。COUNT(*)数行数,COUNT(列名)跳过 NULL,COUNT(DISTINCT 列名)再去重。这个差异在“及格率”那种题里是关键:
-- 及格率:count(条件 or null) / count(*) 是 MySQL 里算比例的老套路 SELECT c.c_id AS '课程id', c.c_name AS '课程名', MAX(s.s_score) AS '最高分', MIN(s.s_score) AS '最低分', ROUND(AVG(s.s_score), 2) AS '平均分', ROUND(COUNT(s.s_score >= 60 OR NULL) / COUNT(s.s_score) * 100, 2) AS '及格率' FROM Score s JOIN Course c ON s.c_id = c.c_id GROUP BY s.c_id, c.c_name;COUNT(s_score >= 60 OR NULL)这个写法利用了COUNT不计 NULL 的特性:条件成立时表达式为 1(非 NULL),不成立时为 NULL(因为FALSE OR NULL是 NULL),于是COUNT刚好数出及格的行数。这个技巧在 MySQL 8 里可以用SUM(s_score >= 60)替代,更直观,判题结果一致。
ONLY_FULL_GROUP_BY是另一个常见报错源:SELECT列表里出现的非聚合列必须全部出现在GROUP BY里,否则在 MySQL 5.7+ 默认配置下直接
本文还有配套的精品资源,点击获取