# 黑马程序员 MySQL数据库入门到精通，从mysql安装到mysql高级、mysql优化全囊括 · 50 集对照笔记

> 本页是学习笔记，不等同于视频逐字稿。原声字幕未取得（匿名接口字幕轨为空且需登录，未使用任何账号），也没有逐句讲解记录；讲义只按节与页码对应，切点未逐句确证。
> 以同版讲义的公开镜像核对章节与页码（镜像非官方出品）；非逐字稿，示例为原创补充。

- 课程页：https://www.bilibili.com/video/BV1Kr4y1i7ru/
- 生成时间（UTC）：2026-10-22T13:12:37Z
- 本文只有笔记文字，不含视频字幕、课件 PDF 原件、媒体文件或任何凭据。
- 看视频时以讲义表为准，跟练时用本站小表；结构相同、行数与数字不同。

## P01 01.MySQL课程介绍

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=1（p=1），时长 11:28
- 讲义：参照前节整理，无独立讲义正文；参照前节 课程介绍（讲义正文从 1.1 数据库相关概念 p1 开始）

### 一句话理解

这一集不写业务 SQL，先讲“为什么学、学什么、怎么安排”：用招聘要求说明数据库的必要性，再把整套课分成基础、进阶、运维三篇。

### 要点

- 为什么学：画面里的 Java 岗位招聘（15–30K·14 薪）反复要求 MySQL、SQL、数据库优化与结构设计——它不是加分项，是后端每天都要用的工具。
- 学什么：基础篇（MySQL 概述 → SQL → 函数/约束 → 多表查询 → 事务）→ 进阶篇（存储引擎、索引、SQL 优化、锁）→ 运维篇（日志、主从复制、分库分表、读写分离）。
- 这套笔记怎么用：一集一页，页首标讲义节名与页码；看视频时对讲义表，跟练时用本站小表；前 50 集全在基础篇，顺序就是上面的基础篇主线。

### 30 秒回忆

岗位要数据库 → 三篇结构 → 前 50 集在基础篇 → 基础篇主线：概述 → SQL → 函数/约束 → 多表 → 事务。

关联课：P02 02. 基础-课程内容-数据库相关概念 · P03 03. 基础-概述-MySQL安装及启动

### 材料与来源

- [平台视频页] B站原视频 P01《01.MySQL课程介绍》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=1
- [平台视频页] 本集采样画面 p01_t30.jpg（约 206s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=1
- [平台视频页] 本集采样画面 p01_t50.jpg（约 343s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=1

证据说明：讲义没有课程介绍正文（1.1 从 p1 开始）；本页按平台标题与两帧画面（招聘要求、三篇结构）整理。原声字幕未取得（匿名接口字幕轨为空），未使用账号；本页不含 SQL 示例。

## P02 02. 基础-课程内容-数据库相关概念

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=2（p=2），时长 9:58
- 讲义：1.1 数据库相关概念 p1（公开镜像；按主题对应，切点 notexact）

### 一句话理解

这一集把三个最容易混用的词分开：数据库是存数据的仓库，DBMS 是管理仓库的软件，SQL 是你跟 DBMS 说话用的语言。

### 步骤

1. 先定义数据库：画面写“存储数据的仓库，数据是有组织的进行存储”，英文 DataBase，简称 DB。关键词是“有组织”——不是把文件堆进文件夹，而是按表、按字段排好。
2. 再区分 DBMS：操纵和管理数据库的大型软件，英文 DataBase Management System。你装的 MySQL 就是 DBMS，数据库是它管着的一份份数据。
3. 然后是 SQL：操作关系型数据库的编程语言，定义了一套统一标准。所以学 MySQL 的 SELECT、INSERT，换到别的关系型数据库大体也能用。
4. 最后提到主流关系型数据库管理系统，画面出现 Oracle 等产品名。它们都走同一条路：客户端发 SQL，DBMS 执行，把结果返回给客户端。

### SQL 示例

```
SHOW DATABASES;
```

说明：这条不需要建表，连上就能跑。结果只有一列 `Database`，每行一个库名，通常能看到 `mysql`、`sys`、`information_schema` 这些系统库。看到多行就说明：一个 DBMS 下面可以同时管很多数据库。

### 易错点

- 把数据库和 DBMS 当成同一个东西，说“我装了一个数据库”。装的是 MySQL 这个 DBMS。
- 以为 SQL 是 MySQL 私有。SQL 是标准，MySQL 有自己的方言，但 SELECT、INSERT 这类主干通用。
- 以为数据库就是一个大文件。它是有组织的数据集合，读写由 DBMS 负责。

### 30 秒速记

DB＝有组织存数据的仓库；DBMS＝管库的软件，MySQL 就是；SQL＝操作关系型库的统一语言；一个 DBMS 可管多个库。

### 自测

1. 数据库、DBMS、SQL 三者是什么关系？
   - 答：数据库是被管理的数据集合；DBMS 是管理它的软件（MySQL 就是 DBMS）；SQL 是客户端交给 DBMS 执行的命令语言。三者是“仓库—管理员—工作语言”的关系。
2. 为什么说 SQL 是“统一标准”，学 MySQL 的语法还有意义吗？
   - 答：主干语法（SELECT、INSERT、UPDATE、DELETE、CREATE TABLE）各关系型数据库大体一致，但分页、日期函数、自增写法等方言不同。学 MySQL 既拿到通用部分，也学到它自己的写法。

### 材料与来源

- [平台视频页] B站原视频 P02《02. 基础-课程内容-数据库相关概念》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=2
- [平台视频页] 本集采样画面 p02_t50.jpg（约 298s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=2

证据说明：以同版讲义公开镜像核对：讲义 1.1 数据库相关概念（p1）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P03 03. 基础-概述-MySQL安装及启动

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=3（p=3），时长 17:24
- 讲义：1.2.1 版本 / 1.2.2 下载 / 1.2.3 安装 / 1.2.4 启动停止 / 1.2.5 客户端连接 p2–13（公开镜像；按主题对应，切点 notexact）

### 一句话理解

这一集在 Windows 上装 MySQL 8.0：用 Installer 配成 Windows 服务、开机自启，再用命令行客户端连上去。

### 步骤

1. 画面先打开一份《MySQL安装文档-Windows》.md，说明安装包和步骤按文档来，不是一路点下一步。随后进入 Installer 的 Check Requirements 和安装类型选择。
2. 到 Windows Service 这一步，勾选 “Configure MySQL Server as a Windows Service” 和 “Start the MySQL Server at System Startup”，服务名默认 `MySQL80`。这一步决定 MySQL 会不会随开机自动跑。
3. 装完在 Windows“服务”里能看到 MySQL80 处于“正在运行”、启动类型“自动”，这是服务已启动的直接证据。服务没起来，客户端一定连不上。
4. 连接方式有两种：图形化工具，或系统自带命令行。幻灯片给的格式是 `mysql [-h 127.0.0.1] [-P 3306] -u root -p`，方括号表示可省略；实际演示只敲了 `mysql -u root -p`，输入密码后出现 `mysql>` 提示符就算连上。注意 `-P` 是大写端口，`-p` 是小写密码。

### SQL 示例

```
SELECT VERSION();
```

说明：在 `mysql>` 提示符后输入，返回一列版本号，例如 `8.0.26`。它验证的不只是“装好了”，而是“客户端确实连上了服务端，并且能执行 SQL”。演示环境是本地 Windows；如果面对的是生产服务器，装之前先找负责人确认，不要自行动 root 密码或开放远程 root。

### 易错点

- 在登录命令里直接写密码（如 `-p123456`），密码会留在命令历史和进程列表里。正确做法是只写 `-p`，等提示再输。
- 以为装完就自动能用。服务没启动、3306 被占用、防火墙拦截，都会连不上，先看服务状态。
- 在生产机上照搬课堂步骤重设 root 密码或开远程访问，可能破坏现有系统。课堂是本地 Windows 环境。
- 把 `-P`（端口）和 `-p`（密码）写反，或者把方括号也照抄进命令。

### 30 秒速记

Installer 装 8.0；勾成 Windows 服务 MySQL80；服务“正在运行”才算启动；连接 `mysql -u root -p`；`-P` 端口、`-p` 密码。

### 自测

1. 为什么装完还要确认 Windows 服务状态？
   - 答：MySQL 是以服务方式运行的：Installer 装完只代表文件就位，服务没启动时 3306 端口没有进程监听，客户端会报“无法连接”。服务状态是“服务端在不在”的直接证据。
2. `mysql -u root -p` 里两个 p 有什么区别？
   - 答：`-P` 大写是端口（默认 3306），`-p` 小写表示“稍后交互式输入密码”。写成 `-p密码` 会把密码暴露在命令历史里。

### 材料与来源

- [平台视频页] B站原视频 P03《03. 基础-概述-MySQL安装及启动》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=3
- [平台视频页] 本集采样画面 p03_t40.jpg（约 417s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=3
- [平台视频页] 本集采样画面 p03_t70.jpg（约 730s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=3

证据说明：以同版讲义公开镜像核对：讲义 1.2.1–1.2.5 安装与连接（p2–13）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P04 04. 基础-概述-数据模型

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=4（p=4），时长 5:57
- 讲义：1.2.6 数据模型 p13–14（公开镜像；按主题对应，切点 notexact）

### 一句话理解

这一集用一张图讲清数据怎么分层放：客户端 → DBMS → 数据库 → 表 → 行和列。

### 步骤

1. 图从左到右：左边是客户端（你的程序或命令行），中间是 DBMS 齿轮，右边是 MySQL 数据库服务器。客户端不直接碰磁盘文件，它把请求交给 DBMS。
2. DBMS 下面挂着多个圆柱形的“数据库”。一个 MySQL 服务可以建很多库，库与库之间彼此独立，同一个服务里库名不能重复。
3. 每个库里放二维表。画面里两张表，表头 `id、name、price、type` 是列（字段），一行 `1、MySQL、100、数据库` 是一条记录。 多张表之间靠一列（比如部门号）关联，这就是后面多表查询的起点。
4. 所以真实顺序是“先建库、再建表、再插行”。后面的 DDL 建库建表、DML 插数据，就是沿着这张图从上往下走。

### SQL 示例

```
SHOW TABLES;
```

说明：先 `USE 你的练习库;` 再执行。结果只有一列 `Tables_in_库名`，每行一张表；空结果说明这个库还是空的。它对应的正是图里“数据库下面挂表”这一层，也提醒你：切错库就会看到另一批表。

### 易错点

- 把“数据库”和“表”混着叫，比如“我建了一个员工数据库”，其实建的是表。
- 以为一行是一条 SQL 语句。行是表里的一条记录，列才是字段。
- 以为客户端直接读写磁盘文件。读写都经过 DBMS，它负责权限、并发和事务。

### 30 秒速记

客户端→DBMS→数据库→表→行/列；一个服务多个库；表头是列、一行是记录；先建库再建表再插数据。

### 自测

1. 一张表里“列”和“行”分别对应什么？
   - 答：列是字段，定义这一类数据的名字和类型（如 name、age）；行是一条完整记录（如 1、小林、22）。建表时设计的是列，插入时增加的是行。
2. 为什么客户端不直接操作数据库文件？
   - 答：文件格式、并发控制、权限和崩溃恢复都由 DBMS 负责。客户端只发 SQL，DBMS 决定怎么读写，这样多个人同时用也不会把数据写坏。

### 材料与来源

- [平台视频页] B站原视频 P04《04. 基础-概述-数据模型》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=4
- [平台视频页] 本集采样画面 p04_t60.jpg（约 214s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=4
- [平台视频页] 本集采样画面 p04_tmid.jpg（中段） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=4

证据说明：以同版讲义公开镜像核对：讲义 1.2.6 数据模型（p13–14）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P05 05. 基础-SQL-通用语法及分类

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=5（p=5），时长 5:51
- 讲义：2.1 通用语法 / 2.2 SQL 分类 p15（公开镜像；按主题对应，切点 notexact）

### 一句话理解

这一集先立规矩：SQL 怎么写才不报错；再把 SQL 按用途分成 DDL、DML、DQL、DCL 四类，后面每集标题都带这个前缀。

### 步骤

1. 通用语法四条：语句可单行或多行书写，用分号结尾；用空格和换行让语句好读；MySQL 不区分大小写，但关键字习惯写大写；注释用 `-- 空格` 或 `#`（MySQL 特有）做单行，`/* */` 做多行。
2. 分类表按“对什么动手”切开：DDL 定义数据库对象（库、表、字段），DML 增删改数据，DQL 查询记录，DCL 建用户、管权限。
3. 记住这个分法，看标题就知道动的是结构还是数据：DDL 建表、DML 插改删、DQL 查询。DCL 在基础篇出现得少，但它管的是“谁能连、能干什么”，不是写业务 SQL 的那部分。
4. 这一集不要求背全表，只要求写语句时条件反射：先想清楚我是在改结构、改数据，还是查数据。

### SQL 示例

```
-- 单行注释：查员工姓名
SELECT name   # 行尾注释也合法
FROM employee;
```

说明：这条把注释、大小写和多行写法都用上了：`--` 后面必须有一个空格，`#` 是 MySQL 特有的行尾注释。结果只有一列 `name`，4 行（小林、小周、小陈、小李）。末尾分号不能省，否则客户端会停在 `->` 等你继续输入。

### 易错点

- `--` 后不加空格（`--注释`）在 MySQL 里不是注释，会被当语法错误。
- 以为“不区分大小写”等于表名也随便写。Linux 上表名大小写常与系统配置有关，统一小写最安全。
- 把 `#` 当标准 SQL 注释。它只在 MySQL 这类数据库可用，换库就报错。

### 30 秒速记

分号结尾；可单行可多行；关键字建议大写；`-- `/`#` 单行、`/* */` 多行；DDL 结构、DML 数据、DQL 查询、DCL 权限。

### 自测

1. DDL 和 DML 的分界线是什么？
   - 答：DDL 动“壳”：建库、建表、改字段、删表；DML 动“里面的行”：插入、修改、删除数据。分界线是操作对象，不是语句长短。
2. 为什么注释写法也会导致报错？
   - 答：注释是给解析器看的语法。`--` 在 MySQL 中要求后面跟空格，否则会被当成减号表达式；`#` 只是 MySQL 方言，其他数据库不一定认。

### 材料与来源

- [平台视频页] B站原视频 P05《05. 基础-SQL-通用语法及分类》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=5
- [平台视频页] 本集采样画面 p05_t20.jpg（约 70s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=5
- [平台视频页] 本集采样画面 p05_t50.jpg（约 175s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=5

证据说明：以同版讲义公开镜像核对：讲义 2.1 通用语法 / 2.2 SQL 分类（p15）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P06 06. 基础-SQL-DDL-数据库操作

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=6（p=6），时长 10:36
- 讲义：2.3.1 数据库操作 p15–17（公开镜像；按主题对应，切点 notexact）

### 一句话理解

这一集只动“库”这一层：查看有哪些库、建库、切库、删库，为后面建表准备一个干净的容器。

### 步骤

1. 查询用 `SHOW DATABASES;` 看全部库，`SELECT DATABASE();` 看当前在哪个库。没选库时后者返回 NULL。
2. 建库用 `CREATE DATABASE 库名`，可加 `IF NOT EXISTS` 避免重复建报错，`DEFAULT CHARSET utf8mb4` 指定字符集，中文才不乱码。
3. 用 `USE 库名;` 切换，之后的建表、插数据都落在当前库里。切错库是新手最常见的“表建到别处去了”。
4. 删库是 `DROP DATABASE [IF EXISTS] 库名`。画面里重复执行 `create database itcast` 时报 database exists，说明不加 IF NOT EXISTS 就会中断脚本。

### SQL 示例

```
CREATE DATABASE IF NOT EXISTS demo_p06 DEFAULT CHARACTER SET utf8mb4;
SHOW DATABASES LIKE 'demo_p06';
```

说明：第一句建一个只用于练习的库，已存在也不报错；第二句只筛出名字叫 `demo_p06` 的库。结果一列（列名会带库名后缀）、一行 `demo_p06`，看到这一行就证明建成功了。只在练习环境执行，别对真实业务库动手。

### 易错点

- 不加 IF NOT EXISTS 反复跑建库脚本，第二次就报错，后面的语句可能全被跳过。
- 建库不指定字符集，默认字符集不合适时中文会变问号。练习统一 `utf8mb4`。
- 以为 DROP DATABASE 只删表。它删的是整个库，里面的表和数据一起没。

### 30 秒速记

SHOW DATABASES 看全部；SELECT DATABASE() 看当前；CREATE DATABASE ... utf8mb4；USE 切库；DROP DATABASE 删整库。

### 自测

1. `USE` 这条命令改变了什么？
   - 答：它只改变当前连接默认使用的库，不复制、不移动数据。之后的建表、查询都默认落在这个库里，直到你再 USE 另一个库。
2. 为什么建库时建议写 `DEFAULT CHARSET utf8mb4`？
   - 答：字符集决定中文、emoji 能不能存。默认字符集随版本和安装配置变化，显式写 utf8mb4 可以避免“中文变问号”这类环境差异。

### 材料与来源

- [平台视频页] B站原视频 P06《06. 基础-SQL-DDL-数据库操作》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=6
- [平台视频页] 本集采样画面 p06_t20.jpg（约 127s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=6
- [平台视频页] 本集采样画面 p06_t40.jpg（约 254s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=6
- [官方文档] MySQL 8.0 Reference Manual: CREATE DATABASE https://dev.mysql.com/doc/refman/8.0/en/create-database.html

证据说明：以同版讲义公开镜像核对：讲义 2.3.1 数据库操作（p15–17）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P07 07. 基础-SQL-DDL-表操作-创建&查询

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=7（p=7），时长 10:35
- 讲义：2.3.2.1 表操作-查询创建 p18–20（公开镜像；按主题对应，切点 notexact）

### 一句话理解

库里要有表才能存数据。这一集学 CREATE TABLE 建表，以及建完怎么查看表列表、表结构和建表语句。

### 步骤

1. 建表语法：`CREATE TABLE 表名(字段 类型 [COMMENT 注释], ...) [COMMENT 表注释];`。每个字段一行更好读，最后一个字段后面不加逗号——画面用红字专门提醒了这点。
2. 建之前先 `USE 库名;`，否则表会建到当前默认库；建完用 `SHOW TABLES;` 确认它到底在哪个库。
3. 查看结构用 `DESC 表名;`，给字段名、类型、是否可为 NULL、键、默认值；想看完整建表语句用 `SHOW CREATE TABLE 表名;`。
4. 画面里的示例表是 `id、name、age、gender` 四列，终端里建的是 `tb_user` 并带 COMMENT。注释不是给数据库看的，是给后来维护的人看的。

### SQL 示例

```
CREATE TEMPORARY TABLE emp_demo (
  id INT COMMENT '编号',
  name VARCHAR(10) COMMENT '姓名',
  age TINYINT UNSIGNED COMMENT '年龄'
) COMMENT '练习用员工表';
DESC emp_demo;
```

说明：`TEMPORARY` 建的是一张只属于当前连接的表，断开就消失，适合练习，不会污染练习库。`DESC` 的结果有 Field、Type、Null、Key、Default、Extra 六列、3 行：看 Field 列确认字段名，看 Type 列确认类型，看 Null 列确认是否允许空。

### 易错点

- 最后一个字段后多写逗号，直接语法错误。
- 不先 USE 就建表，表跑到别的库里，回头 `SHOW TABLES` 找不到。
- 把 COMMENT 当约束。它只是说明文字，不会阻止插入不合规的数据。

### 30 秒速记

先 USE 再建表；字段行末不加逗号；DESC 看结构；SHOW CREATE TABLE 看原始语句；COMMENT 只是注释。

### 自测

1. `DESC` 和 `SHOW CREATE TABLE` 都能看表，有什么区别？
   - 答：DESC 给的是结构化摘要：每个字段的类型、是否可空、键、默认值；SHOW CREATE TABLE 给的是完整建表 SQL，含字符集、注释、索引定义。想快速确认字段用前者，想复刻表用后者。
2. 为什么建表语句要求“最后一个字段后不加逗号”？
   - 答：字段定义之间用逗号分隔，逗号是分隔符而不是结束符。最后多一个逗号意味着解析器在等下一个字段，于是报语法错误。

### 材料与来源

- [平台视频页] B站原视频 P07《07. 基础-SQL-DDL-表操作-创建&查询》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=7
- [平台视频页] 本集采样画面 p07_t50.jpg（约 317s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=7
- [平台视频页] 本集采样画面 p07_t60.jpg（约 380s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=7
- [官方文档] MySQL 8.0 Reference Manual: CREATE TABLE https://dev.mysql.com/doc/refman/8.0/en/create-table.html

证据说明：以同版讲义公开镜像核对：讲义 2.3.2.1 表操作-查询创建（p18–20）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P08 08. 基础-SQL-DDL-数据类型及案例

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=8（p=8），时长 20:44
- 讲义：2.3.2.2 数据类型 / 2.3.2.3 表操作-案例 p20–23（公开镜像；按主题对应，切点 notexact）

### 一句话理解

建表时要给每个字段挑类型：挑小了存不下，挑大了浪费。这一集过一遍数值、字符串、日期三类常用类型，再用员工表案例练手。

### 步骤

1. 数值类型按范围从小到大：TINYINT（1 字节）、SMALLINT、MEDIUMINT、INT（4 字节）、BIGINT（8 字节），还有 FLOAT、DOUBLE 和精确小数 DECIMAL。年龄用 TINYINT UNSIGNED 就够，还顺便挡掉负数。
2. 字符串用 CHAR(n) 定长、VARCHAR(n) 变长。姓名、工号这类短文本用 VARCHAR，长度按业务上限给，不是越大越好。
3. 日期时间：DATE 只要年月日、TIME 只要时分秒、DATETIME 存年月日+时分秒（范围到 9999 年）、YEAR 只存年份、TIMESTAMP 也存日期+时间但只占 4 字节、范围到 2038-01-19。入职时间只要日期就用 DATE；要存「某时刻」用 DATETIME；2038 年以后的时间（比如长期合同到期日）不能用 TIMESTAMP。
4. 案例需求是“设计一张员工信息表”：编号纯数字、工号≤10 位、姓名≤10 位、性别一个汉字、年龄非负、身份证 18 位可能含 X、入职时间到日。逐条翻译成类型，就是下面这条建表语句。

### SQL 示例

```
CREATE TEMPORARY TABLE emp_case (
  id INT COMMENT '编号',
  workno VARCHAR(10) COMMENT '工号',
  name VARCHAR(10) COMMENT '姓名',
  gender CHAR(1) COMMENT '性别',
  age TINYINT UNSIGNED COMMENT '年龄',
  idcard CHAR(18) COMMENT '身份证号',
  entrydate DATE COMMENT '入职时间'
);
DESC emp_case;
```

说明：七个字段对应七条需求：`id` 用 INT；`workno`、`name` 用 VARCHAR(10) 卡住 10 位；`gender` 用 CHAR(1) 正好一个汉字；`age` 用 TINYINT UNSIGNED 保证非负；`idcard` 用 CHAR(18)，因为长度固定且末位可能是 X；`entrydate` 用 DATE。`DESC` 应返回 7 行，逐行看 Type 列就能对上设计。

### 四种日期 / 时间类型的实际取值

```sql
CREATE TABLE t_time (
  d DATE,
  dt DATETIME,
  y YEAR,
  ts TIMESTAMP
);
INSERT INTO t_time VALUES
  ('2026-10-22', '2026-10-22 08:30:00', 2026, '2026-10-22 08:30:00');
SELECT d, dt, y, ts FROM t_time;
```

说明：一行结果：2026-10-22、2026-10-22 08:30:00、2026、2026-10-22 08:30:00。DATE 与 DATETIME 的区别就是「要不要时分秒」；YEAR 只能存年份；TIMESTAMP 范围到 2038 年，超出会报错，而 DATETIME 能存到 9999 年。这里只记用途和范围，不展开时区换算。

预期结果：2026-10-22 | 2026-10-22 08:30:00 | 2026 | 2026-10-22 08:30:00

### 易错点

- 年龄用 INT：能存但占 4 字节，而 TINYINT UNSIGNED 还能顺便挡住负数。
- 身份证用 INT 或 VARCHAR 太短：18 位超出 INT 范围，末位 X 也不是数字，必须用字符型。
- 字符串长度按“现在够用”给。后面加长只能 ALTER，生产上改大表有成本。

### 30 秒速记

整数按范围选；精确小数用 DECIMAL；短文本 VARCHAR、定长 CHAR；日期用 DATE；类型本身就是一层约束。

### 自测

1. 身份证号为什么不能用 INT 存？
   - 答：INT 只有 4 字节，放不下 18 位数字；而且二代身份证末位可能是字母 X，不是数值。CHAR(18) 定长、能存字母，才匹配这个字段。
2. CHAR 和 VARCHAR 该怎么选？
   - 答：长度基本固定的短字段（性别、身份证、国家代码）用 CHAR，省去长度前缀；长度差别大的（姓名、地址）用 VARCHAR，避免定长浪费。

### 材料与来源

- [平台视频页] B站原视频 P08《08. 基础-SQL-DDL-数据类型及案例》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=8
- [平台视频页] 本集采样画面 p08_t20.jpg（约 248s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=8
- [平台视频页] 本集采样画面 p08_t80.jpg（约 994s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=8
- [官方文档] MySQL 8.0 Reference Manual: Data Types https://dev.mysql.com/doc/refman/8.0/en/data-types.html

证据说明：讲义 p20–23「2.3.2.2 数据类型 / 2.3.2.3 案例」为主要依据：数值表、日期时间表（DATE/TIME/YEAR/DATETIME/TIMESTAMP，含 1/3/4/8 字节与范围）与员工表建表案例。原声字幕未取得（匿名接口字幕轨为空），讲师逐句讲解未核；正文示例为原创，四种类型的取值在 MySQL 8.0.46 实跑核对。

## P09 09. 基础-SQL-DDL-表操作-修改&删除

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=9（p=9），时长 10:24
- 讲义：2.3.2.4 表操作-修改 / 2.3.2.5 表操作-删除 p24–25（公开镜像；按主题对应，切点 notexact）

### 一句话理解

表建好后需求会变：加字段、改类型、改字段名、删字段、改表名，最后还有删表。这一集把这些 ALTER 和 DROP 走一遍。

### 步骤

1. 加字段：`ALTER TABLE 表名 ADD 字段 类型 [COMMENT ...]`。画面演示给 emp 加 `nickname varchar(20)`，执行后提示 Query OK。
2. 改字段类型用 MODIFY，改字段名用 CHANGE（CHANGE 要写旧名和新名两遍），删字段用 DROP 字段名，改表名用 `RENAME TO`。
3. 画面里 `ALTER TABLE emp RENAME TO employee;` 之后，再 `DESC emp` 就报 Table doesn't exist，`SHOW TABLES` 里只剩 employee——名字变了，旧名立刻失效。
4. 删表用 `DROP TABLE [IF EXISTS] 表名`，结构和数据一起没；`TRUNCATE TABLE 表名` 只清数据、保留结构，效果像“删掉再建一张空表”。

### SQL 示例

```
CREATE TEMPORARY TABLE emp_alter (id INT, name VARCHAR(10));
ALTER TABLE emp_alter ADD nickname VARCHAR(20) COMMENT '昵称';
ALTER TABLE emp_alter CHANGE nickname nick VARCHAR(20);
DESC emp_alter;
```

说明：先建一张两列的小表，再加 `nickname`，然后把它改名成 `nick`。最后的 `DESC` 应看到 3 行，第三行 Field 是 `nick` 而不是 `nickname`，这就是 CHANGE 的效果。用 TEMPORARY 表练习，断开连接自动消失，不留垃圾。

### 易错点

- CHANGE 写成 `CHANGE 新名 类型`，少了旧名，报语法错误。顺序是旧名、新名、类型。
- 以为 TRUNCATE 能回滚或能只删部分行。它清空整表，不能带 WHERE。
- 生产上直接 DROP TABLE，数据用普通 SQL 找不回。先备份，或先改名观察一段时间。

### 30 秒速记

ADD 加列；MODIFY 改类型；CHANGE 旧名+新名；DROP 删列；RENAME TO 改表名；DROP TABLE 删表、TRUNCATE 清空。

### 自测

1. MODIFY 和 CHANGE 都能改字段，差别在哪？
   - 答：MODIFY 只改类型或属性，字段名不变；CHANGE 必须同时写旧名和新名，所以既能改名也能改类型。只改名时用 CHANGE，只改类型时 MODIFY 更清楚。
2. DROP TABLE 和 TRUNCATE TABLE 的结果差在哪？
   - 答：DROP 把表结构和数据一起删掉，表不存在了；TRUNCATE 保留表结构，只把数据清空并重置自增计数，表还在。

### 材料与来源

- [平台视频页] B站原视频 P09《09. 基础-SQL-DDL-表操作-修改&删除》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=9
- [平台视频页] 本集采样画面 p09_t20.jpg（约 124s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=9
- [平台视频页] 本集采样画面 p09_t80.jpg（约 498s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=9
- [官方文档] MySQL 8.0 Reference Manual: ALTER TABLE https://dev.mysql.com/doc/refman/8.0/en/alter-table.html

证据说明：以同版讲义公开镜像核对：讲义 2.3.2.4 修改 / 2.3.2.5 删除（p24–25）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P10 10. 基础-SQL-DDL小结

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=10（p=10），时长 3:09
- 讲义：参照前节整理，无独立讲义正文；参照前节 2.3.1–2.3.2 DDL（p15–25）

### 一句话理解

这一集把 DDL 压成一页：库操作五条、表操作六条，用来快速回忆和自查漏项。

### 要点

- 库操作五条：`SHOW DATABASES` 看全部、`CREATE DATABASE` 建库、`USE` 切换、`SELECT DATABASE()` 看当前、`DROP DATABASE` 删库。
- 表操作六条：`SHOW TABLES` 看表、`CREATE TABLE` 建表、`DESC` 看结构、`SHOW CREATE TABLE` 看原句、`ALTER TABLE` 增改字段、`DROP TABLE` 删表。
- DDL 改的是“壳”：库和表的结构。DML 才改行；两类记混，后面的增删改查会连锁出错。

### 30 秒回忆

库五条、表六条；DESC 看结构、SHOW CREATE TABLE 看原句；DDL 管壳不管数据。

关联课：P06 06. 基础-SQL-DDL-数据库操作 · P09 09. 基础-SQL-DDL-表操作-修改&删除 · P11 11. 基础-SQL-图形化界面工具DataGrip

### 自测

1. 不看笔记，说出建库到删库最常用的五条语句。
   - 答：SHOW DATABASES; CREATE DATABASE 库名; USE 库名; SELECT DATABASE(); DROP DATABASE 库名; 覆盖查看、创建、切换、确认、删除。

### 材料与来源

- [平台视频页] B站原视频 P10《10. 基础-SQL-DDL小结》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=10
- [平台视频页] 本集采样画面 p10_t60.jpg（约 113s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=10

证据说明：讲义无“小结”正文；本页参照前节 2.3.1–2.3.2 DDL（p15–25）整理。画面 t20 帧为 DDL 小结页，可确认库/表两栏结构；原声字幕未取得，讲师逐句讲解未核。

## P11 11. 基础-SQL-图形化界面工具DataGrip

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=11（p=11），时长 10:43
- 讲义：2.4 图形化界面工具 DataGrip p26–34（公开镜像；按主题对应，切点 notexact）

### 一句话理解

命令行能干活，但看结果、改数据不方便。这一集用 DataGrip 连上 MySQL，在图形界面里建表、执行 SQL、看结果表格。

### 跟播：视频里 ↔ 本站

视频在 DataGrip 里连库、用图形界面建表、执行 SQL、在结果网格里看数据。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| 讲义第 5 章的 emp / dept（本章还没出现） | 本页只有 5 列 employee：id / name / age / dept_id / salary（不建 department 表） |
| dept_id 1 / 2（本页只有数字，没有部门名） | 到 P18–P23 才有 department：1 研发部、2 市场部；P37 起换成 6 列表，2 号叫测试部 |

看完应能：能在图形界面里找到表、执行一条 SELECT、看懂结果网格里的 NULL

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 第一步是新建项目：欢迎界面点 New Project，输入项目名。项目只是把连接和脚本放在一起，它本身不是数据库。
2. 第二步配数据源：填主机（本机就是 localhost 或 127.0.0.1）、端口 3306、用户名和密码，测试连接通过后，左侧才会出现库和表的树。
3. 连上后左边是库/表树，右边是 SQL 编辑器；选中语句执行，结果出现在下方网格，可以排序、筛选、导出。
4. 画面里还用图形化“新建表”填了表名和字段，下方实时生成 `create table` 语句。点 Execute 才真正执行——图形界面只是帮你写 SQL，不是绕过 SQL。

### SQL 示例

```
SELECT * FROM employee;
```

说明：在 DataGrip 的查询控制台里选中这一句执行，下方网格应出现 5 列（id、name、age、dept_id、salary）、4 行。本页练习脚本 p11.sql 建的就是这张 5 列的 employee（独立练习库 mysqlnotes_p11），和后面 P37 那套 6 列的表不是同一张，别混用。`*` 表示所有列；网格里 NULL 会以灰色、斜体之类的特殊样式显示（不同版本可能不同），比命令行更容易一眼看出“这人没填薪水”。

### 易错点

- 以为图形工具连上就等于数据库装好了。工具只是客户端，服务没启动照样连不上。
- 在图形界面点“新建表”后忘了点 Execute，以为表已经建好。
- 在公共电脑上勾选保存密码。多人共用的机器不要留连接密码。

### 30 秒速记

先建项目；再配主机/端口/用户/密码；左侧树看对象；编辑器执行、网格看结果；图形操作最终仍生成 SQL。

### 自测

1. DataGrip 的项目和 MySQL 的数据库是同一件事吗？
   - 答：不是。项目是 DataGrip 自己的工作区，保存连接配置和 SQL 文件；数据库是 MySQL 服务端里的数据容器。一个项目可以连多个数据库。
2. 图形界面建表和手写 CREATE TABLE 有什么区别？
   - 答：结果一样：界面只是把填的字段翻译成 CREATE TABLE 再执行。区别是你看得见生成的 SQL，能顺便学语法，也方便复制到脚本里。

### 材料与来源

- [平台视频页] B站原视频 P11《11. 基础-SQL-图形化界面工具DataGrip》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=11
- [平台视频页] 本集采样画面 p11_t30.jpg（约 192s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=11
- [平台视频页] 本集采样画面 p11_t70.jpg（约 449s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=11

证据说明：以同版讲义公开镜像核对：讲义 2.4 图形化界面工具 DataGrip（p26–34）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P12 12. 基础-SQL-DML-插入

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=12（p=12），时长 14:53
- 讲义：2.5.1 添加数据 p34–37（公开镜像；按主题对应，切点 notexact）

### 一句话理解

表建好后要往里放数据。这一集讲 INSERT 的三种写法：指定字段插、全字段插、一次插多行。

### 步骤

1. 指定字段：`INSERT INTO 表名(字段1,字段2) VALUES(值1,值2);`。字段和值要一一对应，顺序也要对。
2. 全字段：省略字段列表，`INSERT INTO 表名 VALUES(...)`。值的个数和顺序必须和表结构完全一致，表结构一改，这种写法就容易错位。
3. 批量：`VALUES (...),(...),(...)`，一次插多行，比循环单条快，也更不容易出现“插了一半”。
4. 值的类型要和字段匹配：字符串、日期加单引号，数字不加；没写的字段取默认值或 NULL。

### SQL 示例

```
INSERT INTO employee (id, name, age, dept_id, salary)
VALUES (5, '小赵', 24, 2, 9000);
SELECT * FROM employee WHERE id = 5;
```

说明：第一句只写五个字段并按顺序给值：`'小赵'` 是字符串所以要引号，9000 是数字不用。第二句只查 id=5，应返回 1 行：5、小赵、24、2、9000.00（salary 是 DECIMAL(10,2)，所以补两位小数）。先插再查，能确认落库的到底是什么，而不是“以为插进去了”。这张表和数据由本页练习脚本 p12.sql 建好（独立练习库 mysqlnotes_p12）。

### 易错点

- 字段列表和值列表个数不一致，报 Column count doesn't match value count。
- 字符串不加引号，MySQL 可能把它当列名，报 Unknown column。
- 全字段插入依赖列顺序。后来 ALTER 加了列，旧语句就会错位，甚至类型不匹配。
- 主键重复会插入失败；批量插入时一条失败，整条语句都算失败。

### 30 秒速记

指定字段最稳；全字段依赖列顺序；批量用逗号隔开多组值；字符串和日期要加引号。

### 自测

1. 为什么推荐写字段列表，而不是 `INSERT INTO 表 VALUES(...)`？
   - 答：写字段列表后，值的对应关系由名字决定，不受列顺序影响；以后加列或调整顺序，旧语句仍然正确。全字段写法一旦表结构变化就会错位。
2. 一条 INSERT 插三行，第二行主键冲突，结果会怎样？
   - 答：整条语句失败，三行都不会留下（InnoDB 下语句级回滚）。所以批量插入要么全成，要么全不成，不会只进一半。

### 材料与来源

- [平台视频页] B站原视频 P12《12. 基础-SQL-DML-插入》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=12
- [平台视频页] 本集采样画面 p12_t30.jpg（约 267s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=12
- [平台视频页] 本集采样画面 p12_t60.jpg（约 535s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=12
- [官方文档] MySQL 8.0 Reference Manual: INSERT https://dev.mysql.com/doc/refman/8.0/en/insert.html

证据说明：以同版讲义公开镜像核对：讲义 2.5.1 添加数据（p34–37）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P13 13. 基础-SQL-DML-更新和删除

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=13（p=13），时长 8:15
- 讲义：2.5.2 修改数据 / 2.5.3 删除数据 p37–38（公开镜像；按主题对应，切点 notexact）

### 一句话理解

数据写错了要改，不要了要删。这一集讲 UPDATE 和 DELETE，核心只有一句：一定带 WHERE。

### 步骤

1. UPDATE 语法：`UPDATE 表名 SET 字段1=值1, 字段2=值2 [WHERE 条件];`。SET 后面可以一次改多列，用逗号分隔。
2. WHERE 决定改哪些行。不带 WHERE 就是全表每一行都改，这是最典型的事故。
3. DELETE 语法：`DELETE FROM 表名 [WHERE 条件];`。删的是行，表结构还在；不带 WHERE 就是清空整表。
4. 改之前先用同样的 WHERE 写一条 SELECT，看看会命中几行，确认无误再执行 UPDATE 或 DELETE。

### SQL 示例

```
UPDATE employee SET salary = 10500 WHERE id = 2;
SELECT id, name, salary FROM employee WHERE id = 2;
```

说明：第一句只改 id=2 这一行，把薪水设为 10500；第二句回读，应看到 2、小周、10500.00（DECIMAL(10,2) 补两位小数）。用主键做条件最稳，因为主键唯一，最多命中一行。练习时先跑 SELECT 确认命中行数，再改。这张表和数据由本页练习脚本 p13.sql 建好（独立练习库 mysqlnotes_p13）。

### 易错点

- 忘写 WHERE，一条 UPDATE 把全表薪水改成同一个值。执行前先用同样条件 SELECT。
- 以为 DELETE 能删表结构。删结构是 DROP TABLE，DELETE 只删行。
- 用不唯一字段做条件（比如 name），可能一次改到多行。
- 想“删掉某个字段的值”不是 DELETE：DELETE 删整行；把字段置空要用 `UPDATE … SET 字段 = NULL`（字段得允许 NULL）。

### 30 秒速记

UPDATE ... SET ... WHERE；DELETE FROM ... WHERE；不带 WHERE 等于全表；先 SELECT 再改。

### 自测

1. 执行 UPDATE 前最该做的一件事是什么？
   - 答：把同样的 WHERE 放到 SELECT 里先跑一遍，确认命中的行数和内容。行数不对就说明条件写错了，这时还来得及改。
2. DELETE FROM employee; 和 DROP TABLE employee; 有什么区别？
   - 答：前者删掉表里所有行，表还在，还能继续插入；后者把表结构和数据一起删掉，表不存在了。误删数据后想恢复，两者难度也完全不同。

### 材料与来源

- [平台视频页] B站原视频 P13《13. 基础-SQL-DML-更新和删除》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=13
- [平台视频页] 本集采样画面 p13_t20.jpg（约 98s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=13
- [平台视频页] 本集采样画面 p13_t70.jpg（约 346s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=13
- [官方文档] MySQL 8.0 Reference Manual: UPDATE https://dev.mysql.com/doc/refman/8.0/en/update.html

证据说明：以同版讲义公开镜像核对：讲义 2.5.2 修改数据 / 2.5.3 删除数据（p37–38）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P14 14. 基础-SQL-DML小结

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=14（p=14），时长 2:35
- 讲义：参照前节整理，无独立讲义正文；参照前节 2.5.1–2.5.3 DML（p34–38）

### 一句话理解

这一集把 DML 收成三件事：INSERT 加、UPDATE 改、DELETE 删，并再强调一次条件。

### 要点

- 加：`INSERT INTO 表(字段...) VALUES(值...),(值...)`，一条语句可插多行；写列名，列顺序变了也不会错位。
- 改：`UPDATE 表 SET 字段=值 WHERE 条件`，只改命中的行；不写 WHERE 就是全表改。
- 删：`DELETE FROM 表 WHERE 条件` 删的是行，不是表；删表用 `DROP TABLE`。三者共同点：动数据、不动结构。

### 30 秒回忆

INSERT 加、UPDATE 改、DELETE 删；都动行不动壳；每条先想清楚 WHERE。

关联课：P12 12. 基础-SQL-DML-插入 · P13 13. 基础-SQL-DML-更新和删除 · P15 15. 基础-SQL-DQL-基础查询

### 自测

1. 为什么删完还要再查一次？
   - 答：DELETE 可能因条件写错而影响 0 行。回查能确认真的删掉了，而不是以为删了。

### 材料与来源

- [平台视频页] B站原视频 P14《14. 基础-SQL-DML小结》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=14
- [平台视频页] 本集采样画面 p14_t30.jpg（约 46s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=14

证据说明：讲义无“小结”正文；本页参照前节 2.5.1–2.5.3 DML（p34–38）整理。原声字幕未取得（匿名接口字幕轨为空），讲师逐句讲解未核；本页不含 SQL 示例。

## P15 15. 基础-SQL-DQL-基础查询

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=15（p=15），时长 14:26
- 讲义：2.6.1 基本语法 / 2.6.2 基础查询 p41–42（公开镜像；按主题对应，切点 notexact）

### 一句话理解

从这一集开始查数据。基础查询解决三件事：查哪些列、列名怎么显示、重复值要不要去掉。

### 步骤

1. 语法骨架是 `SELECT 字段列表 FROM 表名;`。字段列表写具体列名，或写 `*` 表示所有列；`*` 方便，但会把不需要的大字段也查出来。
2. 起别名用 `AS`：`SELECT name AS 姓名 FROM ...`，结果表头就变成“姓名”。AS 可以省略，写个空格也行，但显式写更好读。
3. 去重是在字段前加 `DISTINCT`：`SELECT DISTINCT dept_id FROM employee;`，相同的值只留一行。它作用于整个字段列表，不是只作用于紧挨着的那一列。
4. 画面里的例子是 `select name, workno, age from emp;`，只取三列，结果按建表时的列顺序显示。

### SQL 示例

```
SELECT name AS 姓名, salary AS 月薪
FROM employee;
```

说明：结果两列，表头是“姓名”和“月薪”，共 4 行。第 4 行小李的月薪显示 NULL——NULL 表示“没有值”，不是 0；这也是后面 COUNT 要区分“数行”和“数列”的原因。

### 易错点

- 以为 `SELECT *` 更快。它往往更慢，因为多读了用不到的列，还多传了网络数据。
- 把 NULL 当 0 或空字符串。它参与运算结果还是 NULL。
- 以为 DISTINCT 只去重一列。`DISTINCT a, b` 去重的是 (a,b) 组合。

### 30 秒速记

SELECT 列 FROM 表；`*` 是所有列；AS 起别名；DISTINCT 去重；NULL 不是 0。

### 自测

1. `SELECT name AS 姓名` 里的 AS 改变了数据吗？
   - 答：没有。它只改结果集的列标题，表里的列名和数据都不变。别名的作用是让输出更好读，或在导出、报表时给出业务名称。
2. `SELECT DISTINCT dept_id, salary` 会得到什么？
   - 答：得到不重复的 (dept_id, salary) 组合。同一部门但薪水不同算两条；只有两列都相同才合并。

### 材料与来源

- [平台视频页] B站原视频 P15《15. 基础-SQL-DQL-基础查询》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=15
- [平台视频页] 本集采样画面 p15_t20.jpg（约 173s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=15
- [平台视频页] 本集采样画面 p15_t70.jpg（约 605s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=15

证据说明：以同版讲义公开镜像核对：讲义 2.6.1 基本语法 / 2.6.2 基础查询（p38–41）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P16 16. 基础-SQL-DQL-条件查询

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=16（p=16），时长 17:57
- 讲义：2.6.3 条件查询 p42–45（公开镜像；按主题对应，切点 notexact）

### 一句话理解

只查需要的行，就要在 WHERE 里写条件。这一集把比较运算符、逻辑运算符和几个特殊写法过一遍。

### 步骤

1. 基本比较有六个：`=`、`>`、`>=`、`<`、`<=`、`<>` 或 `!=`（不等于）。字符串和日期加引号，数字不用。
2. 范围和列表：`BETWEEN 15 AND 20` 含两端，等价于 `>=15 AND <=20`；`IN (18,20,40)` 是多选一，比一串 OR 短。
3. 模糊与空值：`LIKE` 配 `%`（任意多个字符）和 `_`（一个字符）；空值只能写 `IS NULL` 或 `IS NOT NULL`。
4. 多条件用 `AND`/`&&`、`OR`/`||`、`NOT`/`!` 组合。AND 先算、OR 后算，混用加括号最稳。

### SQL 示例

```
SELECT id, name, age, salary
FROM employee
WHERE age BETWEEN 22 AND 30
  AND salary IS NOT NULL;
```

说明：两个条件同时成立才留下：年龄在 22–30（含 22、30），并且薪水不为空。结果是小林（22）、小周（25）、小陈（30）三行；小李年龄 21 不在范围，薪水又是 NULL，被两条条件分别挡掉。

### 易错点

- 写 `salary = NULL`，结果永远查不到。NULL 的比较要用 IS NULL。
- BETWEEN 写反（30 AND 22）不报错，只返回空结果，容易误判成“没数据”。
- `LIKE '%林'` 匹配结尾，`LIKE '林%'` 匹配开头，方向写反就查不到。
- AND 和 OR 混用不加括号。默认 AND 先算，结果常和预期不一致。

### 30 秒速记

六个比较符；BETWEEN 含端点；IN 多选一；LIKE 的 % 和 _；空值用 IS NULL；AND 先于 OR。

### 自测

1. 为什么 `WHERE salary = NULL` 查不到任何行？
   - 答：NULL 表示“未知”，任何与 NULL 的相等比较结果都是 UNKNOWN，不是 TRUE，所以行不会被选中。判断空值必须用 IS NULL 或 IS NOT NULL。
2. `BETWEEN 15 AND 20` 和 `>= 15 AND <= 20` 等价吗？
   - 答：对数字和日期等价，都包含两端。注意 BETWEEN 的下界必须写在前面，写反了虽然不报错，但会得到空结果。

### 材料与来源

- [平台视频页] B站原视频 P16《16. 基础-SQL-DQL-条件查询》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=16
- [平台视频页] 本集采样画面 p16_t30.jpg（约 322s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=16
- [平台视频页] 本集采样画面 p16_t80.jpg（约 861s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=16
- [官方文档] MySQL 8.0 Reference Manual: Comparison Operators https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html

证据说明：以同版讲义公开镜像核对：讲义 2.6.3 条件查询（p42–45）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P17 17. 基础-SQL-DQL-聚合函数

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=17（p=17），时长 6:39
- 讲义：2.6.4 聚合函数 p45–46（公开镜像；按主题对应，切点 notexact）

### 一句话理解

聚合函数把多行压成一个数：一共多少人、平均多少、最大最小、总和。这一集讲 COUNT、AVG、MAX、MIN、SUM。

### 步骤

1. 五个函数各管一件事：`COUNT()` 数行、`AVG()` 平均、`MAX()` 最大、`MIN()` 最小、`SUM()` 求和。参数写列名，`COUNT(*)` 数的是行。
2. 它们都把一整列（或整张表）算成一个值，所以不写 GROUP BY 时，结果只有一行。
3. 关键差别：`COUNT(*)` 数所有行，`COUNT(列名)` 跳过该列为 NULL 的行；AVG 和 SUM 同样忽略 NULL。
4. 所以“平均薪水”用 AVG(salary) 时，没填薪水的人不参与分母。想按“包括没填的人”算，要先想清楚业务定义。

### SQL 示例

```
SELECT COUNT(*) AS 总人数,
       COUNT(salary) AS 有薪水人数,
       AVG(salary) AS 平均薪水
FROM employee;
```

说明：结果一行三列：总人数 4、有薪水人数 3、平均薪水 10000.00。因为小李的 salary 是 NULL，被 COUNT(salary) 和 AVG 跳过；10000 =（8000+10000+12000）÷3，不是除以 4。看这三个数就能理解 NULL 对统计的影响。

### 易错点

- 以为 COUNT(列) 和 COUNT(*) 一样。有 NULL 时结果会差。
- 把 AVG 的分母当总行数。它只算非 NULL 的行。
- 在 WHERE 里写 `WHERE COUNT(*) > 3`。聚合函数不能放在 WHERE，这类筛选要等 GROUP BY/HAVING（后续课）。

### 30 秒速记

COUNT 数、AVG 均、MAX/MIN 极值、SUM 和；不分组只出一行；聚合忽略 NULL。

### 自测

1. 员工 4 人，其中 1 人薪水为 NULL，COUNT(*) 和 COUNT(salary) 各是多少？
   - 答：COUNT(*) 是 4，COUNT(salary) 是 3。前者数行，后者只数该列非 NULL 的值。
2. 为什么 AVG(salary) 不等于 SUM(salary)/COUNT(*)？
   - 答：AVG 的分母是 COUNT(salary)，即非 NULL 的行数；COUNT(*) 把 NULL 行也算进分母，会把平均值拉低。两种算法对应不同的业务口径。

### 材料与来源

- [平台视频页] B站原视频 P17《17. 基础-SQL-DQL-聚合函数》 https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=17
- [平台视频页] 本集采样画面 p17_t30.jpg（约 119s） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=17
- [官方文档] MySQL 8.0 Reference Manual: Aggregate Functions https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html

证据说明：以同版讲义公开镜像核对：讲义 2.6.4 聚合函数（p45–46）。原声字幕未取得（匿名接口字幕轨为空且需登录），讲师逐句讲解未核；本页示例、表与数字为站内原创，不是课堂原表。

## P18 18. 基础-SQL-DQL-分组查询

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=18（p=18），时长 10:18
- 讲义：2.6.5 分组查询 p46–47（公开镜像；按主题对应，切点 notexact）

### 一句话理解

把行按某一列的值分成几堆，再对每一堆算一个汇总数——这就是分组查询。

### 步骤

1. 先用 WHERE 把不要的行筛掉，这时还不知道最后会分几组。比如写 WHERE age < 30，先只留下符合条件的行。
2. 再用 GROUP BY dept_id 按部门把这些行分堆，每个不同的 dept_id 值成为一堆。 分组列可以写多个（如 `GROUP BY gender, dept_id`）：先按第一列分，相同再按第二列分。
3. SELECT 里只写分组列和聚合函数，例如 COUNT(*)、AVG(salary)，数据库会对每一堆各算一个值。
4. 对“堆”的条件写在 HAVING 里，比如只保留人数 ≥ 2 的组；它和 WHERE 的位置不同，作用对象也不同。

### SQL 示例

```
SELECT dept_id, COUNT(*) AS 人数, ROUND(AVG(salary), 0) AS 平均薪资
FROM employee
WHERE age < 30
GROUP BY dept_id
HAVING COUNT(*) >= 2;
```

说明：【原创演示】依赖前面给出的 employee 四行练习数据（小林 22/dept1/8000，小周 25/dept1/10000，小陈 30/dept2/12000，小李 21/NULL/NULL），不用额外建表。WHERE 先去掉 30 岁的小陈；剩下小林、小周在部门 1，小李的 dept_id 是 NULL，自成一堆。HAVING 只留人数 ≥ 2 的堆，于是只剩部门 1：人数 2，平均薪资 9000。结果一行：1、2、9000。

### 易错点

- 在 SELECT 里写 name 这类没参与分组的列：默认开启 ONLY_FULL_GROUP_BY 时会直接报错，而不是随手给你一条记录。
- 把 COUNT(*) >= 2 写进 WHERE：WHERE 执行时还没有“堆”，聚合函数在那里不合法，必须放 HAVING。
- 以为 dept_id 为 NULL 的行会被悄悄丢掉：它单独成一组，COUNT(*) 把它算作 1 行；想排除得显式写 WHERE dept_id IS NOT NULL。

### 30 秒速记

WHERE 筛行 → GROUP BY 分堆 → 聚合函数每堆一值 → HAVING 筛堆；非分组列不进 SELECT。

### 自测

1. 为什么 HAVING COUNT(*) >= 2 不能改写成 WHERE COUNT(*) >= 2？
   - 答：WHERE 在分组之前逐行判断，那时还没有“组”和计数结果；COUNT(*) 是分组后的汇总值，只能由 HAVING 判断。
2. 小李 dept_id 为 NULL，按 dept_id 分组后他会消失吗？
   - 答：不会。GROUP BY 把 NULL 当成一个独立的分组值，他单独成一组；只有写成 COUNT(dept_id) 时这一组的计数才会是 0。
3. 例子里的 ROUND(AVG(salary),0) 为什么放在 SELECT 而不是 WHERE？
   - 答：它是每堆算出来的汇总值，属于分组之后才存在的结果；放进 WHERE 时组还没形成，SQL 算不出来。

### 材料与来源

- [平台视频页] P18 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=18
- [官方文档] MySQL 8.0 Reference：GROUP BY 处理与 ONLY_FULL_GROUP_BY https://dev.mysql.com/doc/refman/8.0/en/group-by-handling.html
- [官方文档] MySQL 8.0 Reference：SELECT 语句 https://dev.mysql.com/doc/refman/8.0/en/select.html

证据说明：按 P18 标题“分组查询”和 9 张采样静帧整理：t20 帧的 Workbench 画面里能看到本集演示注释——按性别分组统计人数、按性别统计平均年龄、先筛年龄<45 再按工作地址分组并要求数量≥3，说明本集讲的是 GROUP BY 与 HAVING 的组合。无字幕，讲师原话与逐条演示结果未核实；正文小表与数字是原创练习数据，不是课堂 emp 表。

## P19 19. 基础-SQL-DQL-排序查询

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=19（p=19），时长 6:36
- 讲义：2.6.6 排序查询 p47–48（公开镜像；按主题对应，切点 notexact）

### 一句话理解

ORDER BY 决定结果按什么顺序排；不写它，数据库给出的行序没有任何保证。

### 步骤

1. 在查询末尾写 ORDER BY 字段名，默认升序 ASC；要降序就在字段后补 DESC。
2. 多列排序从左往右生效：第一列相同的行，才用第二列继续分先后。
3. 排序可以按别名或表达式做，因为这一步发生在结果列确定之后，别名在这里能用。
4. 需要“稳定的翻页顺序”时，末尾补一个唯一列（如 id）当兜底。

### SQL 示例

```
SELECT id, name, salary
FROM employee
ORDER BY salary DESC, id ASC;
```

说明：【原创演示】仍用那四行练习数据。salary 降序：小陈 12000、小周 10000、小林 8000；小李的 salary 是 NULL，在 MySQL 的降序排序里排在最后。第二列 id 只在薪资相同时才起作用，本例没有并列，所以结果顺序是 3、2、1、4。

### 易错点

- 以为 ORDER BY 会改变表里的存储顺序：它只影响这次查询返回的顺序，表里行的物理次序没有变。
- 把 ORDER BY salary, id DESC 当成整体降序：DESC 只跟着紧邻的那一列，第一列仍是升序。
- 排序用了可空列又不看默认位置：升序 NULL 在最前，降序 NULL 在最后，容易误判“最小值”。

### 30 秒速记

ORDER BY 列 [ASC|DESC]，默认升序；多列从左到右；NULL 升序在前、降序在后；翻页要加唯一列兜底。

### 自测

1. ORDER BY salary DESC, id 里的 id 没写 ASC，为什么也按升序？
   - 答：ASC 是默认排序方式，省略即升序；只有需要降序时才必须写 DESC。
2. 按薪资排序后为什么还要加 id？
   - 答：薪资相同的行之间没有规定先后，数据库每次返回的顺序可能不同；只有加唯一列，顺序才确定，翻页才不会重复或漏行。

### 材料与来源

- [平台视频页] P19 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=19
- [官方文档] MySQL 8.0 Reference：SELECT … ORDER BY https://dev.mysql.com/doc/refman/8.0/en/select.html

证据说明：已核对前半段画面，后半演示未取得；排序规则的小例为原创补充。前半段指 t012 的 DQL 目录页与 t020 的“DQL-排序查询 1.语法”页（语法行为 SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1, 字段2 排序方式2）；采样帧止于 t050≈198s，全片 396s，故讲师后半段的演示 SQL 与查询结果未核实，正文也不引用任何课堂数值。无字幕。

## P20 20. 基础-SQL-DQL-分页查询

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=20（p=20），时长 5:34
- 讲义：2.6.7 分页查询 p48（公开镜像；按主题对应，切点 notexact）

### 一句话理解

LIMIT 让查询只返回一段连续的行，用来做“第几页”。

### 步骤

1. 写 LIMIT 起始索引, 查询记录数；起始索引是“跳过多少行”，从 0 开始数。
2. 换算页码：起始索引 =（页码 − 1）× 每页条数。第 2 页每页 10 条就是 LIMIT 10, 10。
3. 第一页可以省略起始索引，直接 LIMIT 10；但这种省略只在第一页成立。
4. 分页前先写 ORDER BY，否则每次“跳过”的可能不是同一批行。

### SQL 示例

```
SELECT id, name
FROM employee
ORDER BY id
LIMIT 2, 2;
```

说明：【原创演示】四行练习数据按 id 升序是 1、2、3、4。LIMIT 2,2 表示跳过前 2 行再取 2 行，所以返回 id 3（小陈）和 id 4（小李）。如果写成 LIMIT 2 则只取前 2 行，含义完全不同。

### 易错点

- 把起始索引当页码：LIMIT 2, 5 是“跳过 2 行取 5 行”，不是“第 2 页”。
- 不带 ORDER BY 就翻页：数据库没有义务保持行序，第 2 页可能出现第 1 页已经出现过的行。
- 以为 LIMIT 是通用写法：分页是各数据库的方言，其它库用 TOP、FETCH 或 ROWNUM。

### 30 秒速记

LIMIT 偏移, 条数；偏移 =（页码−1）×每页条数；第一页可只写条数；分页必须配 ORDER BY。

### 自测

1. 每页 5 条，要第 3 页，LIMIT 后面写什么？
   - 答：LIMIT 10, 5。（3−1）×5 = 10，跳前两页共 10 行，再取 5 行。
2. 既然 LIMIT 能截断，为什么还强调先 ORDER BY？
   - 答：LIMIT 只按数据库返回的顺序截取，而没写 ORDER BY 时这个顺序不确定；同一句 SQL 两次执行可能截到不同的行。

### 材料与来源

- [平台视频页] P20 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=20
- [官方文档] MySQL 8.0 Reference：SELECT … LIMIT https://dev.mysql.com/doc/refman/8.0/en/select.html

证据说明：按 t30/t40/t50/tend 帧核对：画面为“DQL-分页查询 1.语法”，语法行是 SELECT 字段列表 FROM 表名 LIMIT 起始索引, 查询记录数；页内注明起始索引从 0 开始、等于（页码−1）×每页记录数、第一页可简写 limit 10、分页是数据库方言。无字幕，讲师口述与演示结果未核实，小例与数字为原创。

## P21 21. 基础-SQL-DQL-案例练习

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=21（p=21），时长 11:23
- 讲义：2.6.8 案例（5 题） p48–49（公开镜像；按主题对应，切点 notexact）

### 一句话理解

这一集不讲新语法：5 道小题各练一个点——IN + 条件、LIKE 定长、COUNT 分组、双列排序、分页。

### 跟播：视频里 ↔ 本站

视频这一集是「案例」页：5 道小题连着写，题干就在画面上。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| emp 的 name / age / gender / entrydate | employee 的 name / age / gender / entrydate（本站 6 行） |
| 讲义按「20–23 岁女生」「三个字姓名」出题 | 本站同型改写：条件、定长、分组、排序、分页各一题 |

看完应能：5 道题能独立写出，并说清每道题只练了哪一个点

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 每题先只写「要哪些行」：条件放 WHERE，先别管排序和分页，跑一次看行数对不对。
2. 看到「20、21、22、23 岁」这种离散取值用 IN；看到「姓名是三个字」用 LIKE '___'（下划线一个代表一个字符）。
3. 要「每组一个数」才加 GROUP BY + 聚合函数；只是筛行不要分组。
4. 最后补 ORDER BY（多列就写逗号）和 LIMIT（分页是「跳过几条, 取几条」）。每加一层都跑一次。

### SQL 示例

```
-- 表：department(1 研发部, 2 市场部)
-- employee 6 行：小林/周小舟/陈小南/小李/王小明/赵小北（带 gender、entrydate，本页 5 道题要用）
-- 建表与插入语句见本页练习脚本 p21.sql（独立练习库 mysqlnotes_p21，跑一次就有这两张表）

-- 再加一道综合题：每部门人数与平均薪资，只要均值 > 8500 的组，取均值最高的一组
SELECT dept_id, COUNT(*) AS 人数, ROUND(AVG(salary), 0) AS 平均薪资
FROM employee
WHERE salary IS NOT NULL AND dept_id IS NOT NULL
GROUP BY dept_id
HAVING AVG(salary) > 8500
ORDER BY 平均薪资 DESC
LIMIT 1;
```

说明：【原创演示】小李薪资 NULL 被 WHERE 去掉；赵小北部门是 NULL 也被去掉——不删的话 GROUP BY 会把 NULL 单独算成一组，那组只有他一人、均值 15000，会抢到第一名，答案就跑偏了。剩下部门 1（小林 8000、周小舟 10000，均值 9000）和部门 2（陈小南 12000、王小明 9000，均值 10500），两组都过 HAVING；按均值降序第一名是部门 2：人数 2、平均 10500。

### 分题练习（5 题，每题一个目标）

### 题 1：条件 + IN：找出「女生且年龄是 20/21/22/23 岁」的员工。

```sql
SELECT id, name, age FROM employee
WHERE gender = '女' AND age IN (20, 21, 22, 23)
ORDER BY id;
```

提示：两个条件同时成立用 AND；年龄是几个离散值，用 IN 比写四个 OR 短。

答案（先自己写）：3 行：小林 20、周小舟 21、小李 22。

解释：小李 22 岁也在集合里；王小明 35、赵小北 62 被年龄挡掉，陈小南是男生被 gender 挡掉。IN 后面的括号是值列表，不是子查询也可以。

### 题 2：LIKE 定长：姓名正好三个字。

```sql
SELECT id, name FROM employee
WHERE name LIKE '___'
ORDER BY id;
```

提示：一个下划线 _ 匹配一个字符，三个下划线就是「正好三个字」；用 % 会变成「至少」或「包含」。

答案（先自己写）：4 行：周小舟、陈小南、王小明、赵小北。

解释：小林、小李只有两个字，匹配不上三个下划线。改成 LIKE '__' 就只剩他们两个；改成 LIKE '%小%' 会把名字里带「小」的都找出来，含义完全不同。utf8mb4 下 LIKE 按字符匹配，一个汉字算一个 _。

### 题 3：COUNT + 分组：60 岁以下，男、女各多少人。

```sql
SELECT gender, COUNT(*) AS 人数
FROM employee
WHERE age < 60
GROUP BY gender
ORDER BY gender;
```

提示：先用 WHERE 把 60 岁以上的人筛掉，再按性别分组数行数。

答案（先自己写）：2 行：女 3、男 2。

解释：赵小北 62 岁被 WHERE 去掉，所以男生只剩陈小南、王小明 2 人；女生是小林、周小舟、小李 3 人。如果先分组再想筛年龄，条件就得写进 HAVING，那是另一条路（这里不需要）。

### 题 4：双列排序：按年龄升序，年龄相同再按入职时间降序。

```sql
SELECT name, age, entrydate FROM employee
ORDER BY age ASC, entrydate DESC;
```

提示：ORDER BY 后面用逗号分隔多个列，每一列各写自己的 ASC / DESC；第一列相同才看第二列。

答案（先自己写）：6 行：小林 20 → 周小舟 21 → 小李 22 → 陈小南 23 → 王小明 35 → 赵小北 62。

解释：这份数据里 6 个人的年龄都不同，所以第二列暂时没起作用；把某两人年龄改成一样，才会看到「同龄时入职晚的排前面」。ASC 可省略，DESC 不能省。

### 题 5：分页：按年龄升序，取第 2 页，每页 2 条。

```sql
SELECT name, age FROM employee
ORDER BY age ASC, id ASC
LIMIT 2, 2;
```

提示：LIMIT 起始索引, 条数；起始索引 =（页码 − 1）× 每页条数，第 2 页就是 (2−1)×2 = 2。

答案（先自己写）：2 行：小李 22、陈小南 23。

解释：跳过前 2 条（小林、周小舟），取接下来的 2 条。分页一定要配 ORDER BY，否则每次返回的顺序可能不同，「第 2 页」就没有意义。第一页可简写成 LIMIT 2。

### 易错点

- 把「三个字」写成 LIKE '%%%'：% 是任意长度，三个 % 仍然是「任意」，要用三个下划线。
- ORDER BY age, entrydate DESC 误以为两列都降序：DESC 只作用在紧跟它的那一列。
- GROUP BY 会把 dept_id / gender 为 NULL 的行单独算一组，做统计时先想清楚要不要排除。
- LIMIT 2, 2 读成「第 2 条到第 2 条」：它是「跳过 2 条再取 2 条」。

### 30 秒速记

5 题五个点：IN 选离散值、LIKE '___' 定长、COUNT 配 GROUP BY、多列排序各写方向、LIMIT 跳过+取几条。

### 自测

1. 第 2 题如果把 LIKE '___' 改成 LIKE '%小%'，结果会变成什么？
   - 答：会找出名字里含「小」的人：小林、小李、王小明、赵小北等，含义从「正好三个字」变成「包含某个字」。
2. 第 3 题为什么不用 HAVING？
   - 答：因为筛的是「年龄 < 60」这类行级条件，在分组前用 WHERE 就够了；HAVING 是给分组后的聚合值用的。

### 材料与来源

- [平台视频页] P21 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=21
- [官方文档] MySQL 8.0 Reference：SELECT 语句 https://dev.mysql.com/doc/refman/8.0/en/select.html

证据说明：题干见讲义 p48–49「2.6.8 案例」五题（画面 t≈60s 的题目页与讲义逐条对应），本页按同型改写、用自己的 6 行小表，不照抄原题句子与讲义 17 行 emp 数据。原声字幕未取得（匿名接口字幕轨为空），讲师逐句解法未核；示例数字均在 MySQL 8.0.46 实跑核对。

## P22 22. 基础-SQL-DQL-执行顺序

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=22（p=22），时长 7:57
- 讲义：2.6.9 执行顺序 p49–50（公开镜像；按主题对应，切点 notexact）

### 一句话理解

SQL 的书写顺序和执行顺序不是一回事：你从 SELECT 开始写，数据库却先找表、再筛行、再分组，最后才挑列。

### 步骤

1. FROM：先确定数据来自哪张表，这是第一步。
2. WHERE：对行做过滤，此时 SELECT 里的别名还不存在。
3. GROUP BY 分堆、HAVING 筛堆；聚合值到这一步才算出来。
4. SELECT 取出要显示的列，然后 ORDER BY 排序、LIMIT 截断。

### SQL 示例

```
SELECT id, name, salary * 12 AS 年薪
FROM employee
WHERE salary IS NOT NULL
ORDER BY 年薪 DESC;
```

说明：【原创演示】四行练习数据里小李没有薪资，WHERE 先把他去掉；SELECT 用 salary*12 算出年薪并起别名，ORDER BY 才能直接用“年薪”。结果三行：小陈 144000、小周 120000、小林 96000。若把条件换成 WHERE 年薪 > 90000 会直接报错（Unknown column），因为 WHERE 阶段别名还没生成。

### 易错点

- 以为 SELECT 写在最前面就先执行，于是在 WHERE 里用别名：会报未知列。
- 把 HAVING 当 WHERE 用：能在 WHERE 做的行过滤不要拖到 HAVING，语义不同还白算一遍分组。
- 把这套顺序当成引擎真实的指令流水线：它是解释“别名、聚合能写在哪”的逻辑顺序，优化器实际怎么取数另说。

### 30 秒速记

书写：SELECT→FROM→WHERE→GROUP BY→HAVING→ORDER BY→LIMIT；理解：FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT。

### 自测

1. ORDER BY 能用 SELECT 里的别名，WHERE 不能，为什么？
   - 答：按逻辑顺序 WHERE 在 SELECT 之前执行，那时别名还没被定义；ORDER BY 排在 SELECT 之后，所以能引用。
2. 为什么说“执行顺序”不等于引擎的指令序列？
   - 答：它是 SQL 语义的分层理解方式，用来判断每个子句能看到什么；真实执行由优化器决定访问路径和顺序，可能合并或跳过步骤。

### 材料与来源

- [平台视频页] P22 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=22
- [官方文档] MySQL 8.0 Reference：SELECT 语句 https://dev.mysql.com/doc/refman/8.0/en/select.html

证据说明：按 t20 帧核对：幻灯片“DQL-执行顺序”左侧黄色框是编写顺序 SELECT/FROM/WHERE/GROUP BY/HAVING/ORDER BY/LIMIT，红圈数字可见 FROM=1、WHERE=2、GROUP BY=3、SELECT=4；该帧是动画中间态，HAVING/ORDER BY/LIMIT 的编号未出现，完整顺序按 SQL 标准语义补全，并已在正文说明这是逻辑理解顺序而非引擎指令序列。无字幕。

## P23 23. 基础-SQL-DQL小结

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=23（p=23），时长 3:11
- 讲义：参照前节整理，无独立讲义正文；参照前节 2.6 DQL（p38–50）

### 一句话理解

这一集把 DQL 的六个子句收成一张图：谁筛行、谁筛组、别名能写在哪。

### 要点

- 六个子句：`SELECT 字段 FROM 表 WHERE 行条件 GROUP BY 分组列 HAVING 组条件 ORDER BY 排序列 LIMIT 分页`。
- 两个过滤位置：WHERE 在分组前筛行；HAVING 在分组后筛组，可以写聚合值（`HAVING COUNT(*) >= 2`）。
- 别名只向后生效：ORDER BY 能用 `SELECT` 里起的别名，WHERE 不能用。

### 30 秒回忆

六子句、两个过滤位置（WHERE 行 / HAVING 组）、别名只向后生效。

关联课：P18 18. 基础-SQL-DQL-分组查询 · P19 19. 基础-SQL-DQL-排序查询 · P20 20. 基础-SQL-DQL-分页查询 · P22 22. 基础-SQL-DQL-执行顺序

### 自测

1. 哪两个子句最容易混？依据什么区分？
   - 答：WHERE 和 HAVING。依据是作用对象与时机：WHERE 对行、在分组前；HAVING 对组、在分组后，可以包含聚合值。

### 材料与来源

- [平台视频页] P23 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=23
- [官方文档] MySQL 8.0 Reference：SELECT 语句 https://dev.mysql.com/doc/refman/8.0/en/select.html

证据说明：讲义无“小结”正文；本页参照前节 2.6 DQL（p38–50）整理。画面 t60 帧为 DQL 小结页，列出 SELECT/FROM/WHERE/GROUP BY/HAVING 与运算符清单；原声字幕未取得，讲师逐句讲解未核。

## P24 24. 基础-SQL-DCL-用户管理-

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=24（p=24），时长 10:58
- 讲义：2.7.1 管理用户 p51（公开镜像；按主题对应，切点 notexact）

### 一句话理解

DCL 管的是“谁能连进来”：建用户、改密码、删用户；账号由用户名和主机名一起定位。

### 步骤

1. 先看现有账号：USE mysql; SELECT user, host FROM user; 只读查看，不改任何东西。
2. 建用户：CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码'；主机名 localhost 只允许本机，% 允许远程。
3. 改密码：ALTER USER '用户名'@'主机名' IDENTIFIED BY '新密码'。
4. 删用户：DROP USER '用户名'@'主机名'。

### SQL 示例

```
CREATE USER 'mysqlnotes_demo'@'localhost' IDENTIFIED BY 'LocalOnly_ChangeMe_01';
ALTER USER 'mysqlnotes_demo'@'localhost' IDENTIFIED BY 'LocalOnly_ChangeMe_02';
DROP USER 'mysqlnotes_demo'@'localhost';
```

说明：【原创演示】三句按顺序演示“建—改—删”，全部限定 localhost，只用于本机练习库。两个密码串是明显的占位值，不是任何真实口令；真机上要换成自己保管的强密码，并且不要把密码写进脚本或提交进仓库。p24.sql 里那两句 `SELECT ... FROM mysql.user` 只是只读查看账号表，别直接改这张系统表。这些语句只在本机练习库和临时账号上有意义；本页原创例已在隔离的 MySQL 8.0.46 测试实例执行通过（账号用完即删），不是视频里那次演示的复现。

### 易错点

- 图省事建一个能从任意主机登录的管理员账号，或把全部权限一次性给业务账号：等于把整台实例交给任何能连上的人；练习只用最小权限的 localhost 账号。
- 漏写或写错主机名：'u'@'localhost' 与 'u'@'%' 是两个不同账号，删掉一个另一个还在。
- 把密码硬编码进脚本或截图：示例里只能出现占位串，真实密码应由使用者在本地输入并妥善保管。

### 30 秒速记

账号 = 用户名 + 主机名；CREATE/ALTER/DROP USER；localhost 与 % 是两个账号；密码只放占位。

### 自测

1. 为什么建用户必须写主机名？
   - 答：MySQL 用“用户名@主机名”共同标识账号，同一用户名从不同来源登录可以是不同账号，权限各自独立。
2. DROP USER 之后，这个账号的权限记录会残留吗？
   - 答：不会。DROP USER 会连同该账号在权限表里的记录一起删除，“只删账号不删权限”的担心在这里不成立。

### 材料与来源

- [平台视频页] P24 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=24
- [官方文档] MySQL 8.0 Reference：CREATE USER https://dev.mysql.com/doc/refman/8.0/en/create-user.html
- [官方文档] MySQL 8.0 Reference：账号管理语句 https://dev.mysql.com/doc/refman/8.0/en/account-management-statements.html

证据说明：按 t20 帧核对：幻灯片“DCL-管理用户”列出四件事——查询用户（USE mysql; SELECT * FROM user;）、创建用户（CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码'）、修改密码（ALTER USER … IDENTIFIED WITH mysql_native_password BY '新密码'）、删除用户（DROP USER）。正文示例为原创改写，用 localhost 练习账号和明显占位密码，未使用视频里的账号名与密码；无字幕，讲师演示过程未核实，也未在任何实例上执行。

## P25 25. 基础-SQL-DCL-权限控制-

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=25（p=25），时长 8:25
- 讲义：2.7.2 权限控制 p52（公开镜像；按主题对应，切点 notexact）

### 一句话理解

权限控制决定“连进来以后能做什么”：给账号授权、收回权限，粒度可以细到某库某表。

### 步骤

1. 先有账号再授权：`CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';` 建号，然后 `GRANT 权限列表 ON 库名.表名 TO '用户名'@'主机名';` 授权，多个权限用逗号分隔。MySQL 8 的 GRANT 不会自动建号，账号不存在会报 1410。
2. 收回：REVOKE 权限列表 ON 库名.表名 FROM '用户名'@'主机名'。
3. 查看：SHOW GRANTS FOR '用户名'@'主机名'，确认实际生效的权限。
4. 用该账号实际连一次，验证能查、不能改，而不是只看语句执行成功。

### SQL 示例

```
-- 本机学习演示：账号只在本机，脚本结束就删号；密码是写出来给人看的占位串，不是真实口令
DROP USER IF EXISTS 'mysqlnotes_demo'@'localhost';
CREATE USER 'mysqlnotes_demo'@'localhost' IDENTIFIED BY 'LocalOnly_Demo_ChangeMe_01';
GRANT SELECT, INSERT ON mysqlnotes_p25.employee TO 'mysqlnotes_demo'@'localhost';
SHOW GRANTS FOR 'mysqlnotes_demo'@'localhost';
REVOKE INSERT ON mysqlnotes_p25.employee FROM 'mysqlnotes_demo'@'localhost';
DROP USER 'mysqlnotes_demo'@'localhost';
```

说明：【原创演示】这是本机学习用的演示账号：`'mysqlnotes_demo'@'localhost'` 只在你自己机器上存在，由本页练习脚本 p25.sql 建库、建号，结束时删号，和视频里的 itcast/heima 账号无关；密码 `LocalOnly_Demo_ChangeMe_01` 是明显的占位串，不是任何真实口令或生产凭据。这几句演示“建号 → 给 mysqlnotes_p25.employee 授查+插 → SHOW GRANTS 复核 → 收回插入只留查询 → 删号”，授权范围写死到库.表，不用 *.*。本页原创例已在隔离的 MySQL 8.0.46 实例跑通（临时库 + 临时账号，结束即清理）；真实环境请换成自己保管的强密码，别把密码写进仓库。

### 易错点

- 图快把全部权限一次性授到整个实例：一旦出问题既难定位也难收窄；应当逐项授权、限定到库.表。
- 只授权不看 SHOW GRANTS：账号可能还带着之前的旧权限，改完必须复核。
- 直接改 mysql 库的权限表就以为生效：手工改表需要额外刷新权限，正规做法是用 GRANT/REVOKE。

### 30 秒速记

GRANT/REVOKE 权限 ON 库.表 TO/FROM 账号；SHOW GRANTS 复核；范围越小越好。

### 自测

1. 为什么示例把权限限定到 mysqlnotes_p25.employee，而不是整库？
   - 答：权限范围越小，误用和被盗的影响面越小；只读账号不需要同库其它表的写权限。
2. 授予成功后怎样确认真的生效？
   - 答：先用 SHOW GRANTS 看记录，再用该账号实际连一次执行允许和不允许的操作；两步都过才算验证。

### 材料与来源

- [平台视频页] P25 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=25
- [官方文档] MySQL 8.0 Reference：GRANT 语句 https://dev.mysql.com/doc/refman/8.0/en/grant.html

证据说明：P25 标题为“DCL-权限控制”。采样帧 t40 可见讲师在 Workbench 里演示建号/改密/删号（账号名形如 itcast@localhost、heima@%），具体授权语句未在帧中取得；正文 GRANT/REVOKE 示例按 MySQL 8.0 语法原创编写，使用 localhost 演示账号与独立练习库 mysqlnotes_p25；本页原创例已在隔离的 MySQL 8.0.46 实测通过（账号建完即删，公开的密码是本机占位串，不含真实凭据），未照抄视频账号。

## P26 26. 基础-SQL-DCL小结

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=26（p=26），时长 2:56
- 讲义：参照前节整理，无独立讲义正文；参照前节 2.7 DCL（p50–53）

### 一句话理解

DCL 小结只有两块：用户管理和权限控制。

### 要点

- 用户管理：`CREATE USER`、`ALTER USER`、`DROP USER`；账号写成 `用户名@主机名`，两段一起才唯一。
- 权限控制：`GRANT` 授权、`REVOKE` 收回，`SHOW GRANTS` 复核；权限可以细到某库某表。
- 只在本地测试库、用最小权限账号练；DCL 改的是“谁能做什么”，不碰表里的数据。

### 30 秒回忆

用户管理 + 权限控制；账号=用户名@主机名；GRANT/REVOKE/SHOW GRANTS；本地最小权限。

关联课：P24 24. 基础-SQL-DCL-用户管理- · P25 25. 基础-SQL-DCL-权限控制-

### 自测

1. DCL 和 DML 改的东西有什么不同？
   - 答：DML 改表里的行数据；DCL 改账号与权限这类“谁能做什么”的规则，不碰业务数据。

### 材料与来源

- [平台视频页] P26 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=26
- [官方文档] MySQL 8.0 Reference：账号管理语句 https://dev.mysql.com/doc/refman/8.0/en/account-management-statements.html

证据说明：讲义无“小结”正文；本页参照前节 2.7 DCL（p50–53）整理。画面 t20–tend 帧为小结页，只有“用户管理 / 权限控制”两条；原声字幕未取得，讲师逐句讲解未核。

## P27 27. 基础-函数-字符串函数

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=27（p=27），时长 15:09
- 讲义：3.1 字符串函数 p54–56（公开镜像；按主题对应，切点 notexact）

### 一句话理解

字符串函数把文本当材料加工；最常用的是补位——工号不足 5 位就在左边补 0。

### 跟播：视频里 ↔ 本站

视频先过七个字符串函数，最后给「工号统一 5 位、不足补 0」的练习页。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| emp.workno（讲义的员工工号列） | employee.workno（本站练习表新增的一列） |
| update emp set workno = lpad(workno, 5, '0') | update employee set workno = lpad(workno, 5, '0')（同一写法） |

看完应能：能先 SELECT 预览、再 UPDATE，并说清 LPAD 三个参数各是什么

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 补位：LPAD(原串, 目标总长度, 填充串) 在左边补到指定长度，RPAD 在右边补。三个参数各管一件事：处理谁、最终几位、用什么补。
2. 先预览再改：`SELECT workno, LPAD(workno, 5, '0')` 看结果对不对，再写 `UPDATE … SET workno = LPAD(workno, 5, '0')`，最后回读确认。
3. 拼接与大小写：CONCAT(a,b) 连接字符串，LOWER/UPPER 统一大小写；清理用 TRIM 去首尾空格，截取用 SUBSTRING(串, 起始位置, 长度)，位置从 1 起。
4. 数字符还是数字节：CHAR_LENGTH 数「字」，LENGTH 数「字节」，中文在 utf8mb4 下占 3 字节。

### SQL 示例

```
-- 表：employee(id, name, workno)，工号现在是 '7'、'12'、'345'
-- 建表与插入见本页练习脚本 p27.sql（独立练习库 mysqlnotes_p27）

-- 第 1 步：先预览，不改数据。LPAD(原串, 目标总长度, 填充串)
SELECT id, workno, LPAD(workno, 5, '0') AS 补齐后
FROM employee
ORDER BY id;

-- 第 2 步：确认预览对了，再把函数写进 UPDATE 的列位置
UPDATE employee
SET workno = LPAD(workno, 5, '0');

-- 第 3 步：回读，确认列真的改了
SELECT id, workno FROM employee ORDER BY id;
```

说明：第 1 步结果三行：00007、00012、00345。三个参数分别是：要处理的字符串（这里是列 workno）、目标总长度 5、左边填充的字符 '0'。它保证「最终长度是 5」，所以比 5 位长的工号会被截断（LPAD('1234567',5,'0') 得 '12345'），不是只补不截。第 2 步影响 3 行——函数套在列上会对每一行各算一次；改之前先 SELECT 预览，是因为 UPDATE 写错要再改回来。第 3 步回读应看到 00007、00012、00345。

### 顺手验证：LPAD 超长会截断

```sql
SELECT LPAD('7', 5, '0') AS 补零, LPAD('1234567', 5, '0') AS 太长被截断;
```

说明：一行：00007、12345。目标长度是硬指标——短了补、长了截，所以别把它当「只追加」。

预期结果：00007、12345

### 其他函数速查

| 函数 | 做什么 | 一句话例子 |
|---|---|---|
| CONCAT(s1, s2, …) | 拼接成一个字符串 | CONCAT('emp-', '007') → emp-007 |
| LOWER / UPPER | 转小写 / 大写 | UPPER('mysql') → MYSQL |
| RPAD(s, n, pad) | 右侧填充到 n 个字符 | RPAD('7', 3, '-') → 7-- |
| TRIM(s) | 去掉首尾空格 | TRIM(' mysql ') → mysql |
| SUBSTRING(s, start, len) | 从第 start 位取 len 个字符（从 1 起） | SUBSTRING('mysql', 2, 3) → ysq |
| CHAR_LENGTH / LENGTH | 字符数 / 字节数 | CHAR_LENGTH('数据库')=3，LENGTH('数据库')=9 |

### 易错点

- 用 LENGTH 判断字符数：中文一个字多个字节，LENGTH('数据库')=9 而字符数是 3，长度校验会误判。
- 以为 LPAD 只会补：目标长度比原串短时它会截断，LPAD('abcdef',3,'0') 得 'abc'。
- SUBSTRING 起始位置写 0 以为从第一个字符开始：MySQL 位置从 1 计，写 0 得到空串。
- 不预览直接 UPDATE：改错要再写一条 UPDATE 改回来，先 SELECT 一遍成本更低。

### 30 秒速记

LPAD(原串, 总长度, 填充串) 三个参数；先预览再 UPDATE 再回读；位置从 1 起；CHAR_LENGTH 字符、LENGTH 字节。

### 自测

1. 为什么校验“姓名不超过 10 个字”要用 CHAR_LENGTH 而不是 LENGTH？
   - 答：LENGTH 返回字节数，utf8mb4 下一个汉字 3 字节，10 个汉字会被算成 30，校验立刻误判；CHAR_LENGTH 才是字符个数。
2. LPAD('7',3,'0') 和 LPAD('1234',3,'0') 分别得到什么？为什么？
   - 答：'007' 和 '123'。LPAD 的目标是“最终长度为 3”，不足就补、超出就截断，它不保证只在左侧追加。

### 材料与来源

- [平台视频页] P27 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=27
- [官方文档] MySQL 8.0 Reference：字符串函数 https://dev.mysql.com/doc/refman/8.0/en/string-functions.html

证据说明：按 t30 帧核对：画面为「字符串函数」章节页；t80 帧是练习页，题干要求把员工工号统一成 5 位、不足补 0（示例 1 号写成 00001），与讲义 p56 案例的 `update emp set workno = lpad(workno, 5, '0')` 对应。函数清单另由 P31 小结页与讲义 p55 表格（CONCAT、LOWER、UPPER、LPAD、RPAD、TRIM、SUBSTRING）核对。原声字幕未取得，讲师逐条演示与结果未核实；正文示例与数字为原创，并在 MySQL 8.0.46 实跑核对。

## P28 28. 基础-函数-数值函数

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=28（p=28），时长 9:16
- 讲义：3.2 数值函数 p56–57（公开镜像；按主题对应，切点 notexact）

### 一句话理解

数值函数对数字做取整、取余、四舍五入这类加工，常用来把计算结果修成想要的精度。

### 步骤

1. 向上 / 向下取整：CEIL(1.2)=2，FLOOR(1.9)=1；注意负数时方向是“往大 / 往小”。
2. 取余：MOD(7,3)=1，等价于 7 % 3。
3. 四舍五入到指定位数：ROUND(值, 小数位数)，省略位数就取整。
4. 随机数：RAND() 返回 0 到 1 之间的小数。讲义 p57 的六位验证码就是三步组合：RAND 取随机 → 乘 1000000 再 ROUND 取整 → LPAD 补到 6 位。

### SQL 示例

```
SELECT CEIL(1.2) AS 上取整,
       FLOOR(1.9) AS 下取整,
       MOD(7, 3) AS 余数,
       ROUND(3.14159, 2) AS 两位小数,
       ROUND(2.5) AS 取整;
```

说明：【原创演示】全部是对常量的计算，不依赖任何表。结果一行：2、1、1、3.14、3。ROUND(2.5) 得 3，说明它按“远离 0”的方向处理 .5，而不是银行家舍入。

### 易错点

- 以为负数取整方向不变：CEIL(-1.2) 得 -1、FLOOR(-1.2) 得 -2，方向是“往大 / 往小”，不是“去掉小数”。
- 拿 ROUND 处理金额：浮点数只是近似值，ROUND(2.675,2) 可能得到 2.67；金额用 DECIMAL 存、按分计算。
- 在 WHERE 里对列套 ROUND 再比较：条件变成逐行现算，和直接比较原值不等价，边界行容易漏。

### 30 秒速记

CEIL 向上、FLOOR 向下、MOD 取余、ROUND 定位数（.5 远离 0）、RAND 随机；金额别靠浮点 ROUND。

### 自测

1. ROUND(2.5) 为什么是 3 而不是 2？
   - 答：MySQL 对 .5 采用远离 0 的方向取整，2.5 进到 3；负数的 -2.5 会得到 -3。
2. 为什么金额不建议用 ROUND 直接处理浮点列？
   - 答：浮点列存的是近似值，参与运算后可能与看上去的值略有偏差，四舍五入结果会出乎意料；用 DECIMAL 精确存再算才可控。

### 材料与来源

- [平台视频页] P28 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=28
- [官方文档] MySQL 8.0 Reference：数值函数 https://dev.mysql.com/doc/refman/8.0/en/mathematical-functions.html

证据说明：P28 标题为“数值函数”。采样帧多为代码区，未取得函数清单页；函数名与示例来自 P31 小结页（CEIL、FLOOR、MOD、RAND、ROUND）与 MySQL 8.0 数值函数文档对照，正文示例为原创，讲师演示与结果未核实；无字幕。

## P29 29. 基础-函数-日期函数

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=29（p=29），时长 9:27
- 讲义：3.3 日期函数 p58–59（公开镜像；按主题对应，切点 notexact）

### 一句话理解

日期函数用来取当前时间、拆出年月日、做日期加减，以及算两个日期差多少天。

### 步骤

1. 取当前：CURDATE() 只要日期，CURTIME() 只要时间，NOW() 日期时间都要。
2. 拆字段：YEAR / MONTH / DAY 从日期里取出对应部分。
3. 加减：DATE_ADD(日期, INTERVAL n DAY)，单位可换成 MONTH、YEAR。
4. 算差：DATEDIFF(d1,d2) 得 d1−d2 的天数；算整年数用 TIMESTAMPDIFF(YEAR, 起始, 结束)。

### SQL 示例

```
SELECT DATEDIFF('2026-10-22', '2026-10-01') AS 相差天数,
       TIMESTAMPDIFF(YEAR, '2004-12-31', '2026-10-22') AS 周岁,
       YEAR('2004-12-31') AS 出生年,
       DATE_ADD('2026-10-22', INTERVAL 7 DAY) AS 一周后;
```

说明：【原创演示】全部用固定日期字面量，不写 NOW()，所以每次结果都一样。差值 21 天；2004-12-31 出生到 2026-10-22 还没过生日，周岁是 21，而 2026−2004=22 只是年份相减；一周后是 2026-10-29。

### 易错点

- 用 YEAR(今天) − YEAR(生日) 当年龄：没过生日的人会被多算一岁，差一天就错。
- DATEDIFF 参数顺序记反：DATEDIFF(a,b) 是 a−b，反过来符号相反。
- 在示例和测试里写 NOW()：每次运行结果都变，断言无法复现；固定日期字面量才能稳定对比。

### 30 秒速记

CURDATE/CURTIME/NOW、YEAR/MONTH/DAY、DATE_ADD、DATEDIFF(a−b)、TIMESTAMPDIFF 算整年；测试用固定日期。

### 自测

1. 为什么算年龄不能用 YEAR(CURDATE()) − YEAR(birthday)？
   - 答：它只比较年份，忽略今年生日是否已过；应使用 TIMESTAMPDIFF(YEAR, birthday, CURDATE())，它按完整年数计算。
2. DATEDIFF('2026-10-01','2026-10-22') 是正还是负？
   - 答：负。函数是前一个日期减后一个日期，10-01 减 10-22 得 −21。

### 材料与来源

- [平台视频页] P29 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=29
- [官方文档] MySQL 8.0 Reference：日期与时间函数 https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html

证据说明：按 t012/t040 帧核对：画面标题为“日期函数”，其余帧是代码区，未取得完整函数表。函数名另由 P31 小结页（CURDATE、CURTIME、NOW、YEAR、MONTH、DAY、DATE_ADD、DATEDIFF）核对；TIMESTAMPDIFF 属文档补充，正文已单独说明用途。示例为原创且使用固定日期；无字幕。

## P30 30. 基础-函数-流程函数

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=30（p=30），时长 12:01
- 讲义：3.4 流程函数 p59–61（公开镜像；按主题对应，切点 notexact）

### 一句话理解

流程函数让一行数据按条件走不同分支：两选一用 IF，空值兜底用 IFNULL，分档用 CASE。

### 跟播：视频里 ↔ 本站

视频演示 IF、IFNULL 与两种 CASE，并用「工作地址分档」做例子。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| emp.salary / emp.dept_id / emp.workaddress | employee.salary / employee.dept_id（本站没有 workaddress 列） |
| IF(value,t,f)、CASE 列 WHEN 值 | 同左，示例换成薪资与部门两列 |

看完应能：能分清 IF 两选一、简单 CASE 等值分档、搜索 CASE 条件分档，并知道 NULL 要用 IS NULL

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. IF(条件, 真值, 假值)：只做两选一。比如「有没有定薪」——条件成立给一个值，否则给另一个。
2. IFNULL(值, 兜底值) 只在值为 NULL 时返回兜底；NULLIF(a,b) 在 a 等于 b 时返回 NULL，用来把某个值「变成空」。
3. CASE 有两种写法：`CASE 列 WHEN 值 THEN …` 只能做等值分档（列名只写一次）；`CASE WHEN 条件 THEN …` 能写范围和多条件。
4. 分支顺序很重要：CASE 自上而下，命中即停；范围宽的条件放前面会把后面的分支挡掉，所以先具体后宽泛。

### SQL 示例

```
SELECT name,
       IFNULL(salary, 0) AS 薪资显示,
       CASE WHEN salary IS NULL THEN '未定薪'
            WHEN salary >= 10000 THEN '高'
            ELSE '普通' END AS 档位
FROM employee
ORDER BY id;
```

说明：【原创演示】四行练习数据逐行判断：小林 8000 → 普通；小周 10000 → 高；小陈 12000 → 高；小李 salary 为 NULL，IFNULL 显示 0，CASE 第一个分支命中得“未定薪”。结果四行按 id 排列。如果把 CASE 写成 `CASE WHEN salary >= 10000 THEN '高' ELSE '普通' END`（漏掉 IS NULL 分支），NULL 参与比较得到未知、不成立，就会落到 ELSE 变成「普通」——没定薪的人被算成普通，语义就错了。

### IF：两选一（有没有定薪）

```sql
SELECT id, name,
       IF(salary IS NULL, '未定薪', '已定薪') AS 状态
FROM employee
ORDER BY id;
```

说明：四行：小林、小周、小陈是「已定薪」，小李是「未定薪」。IF 的三个参数是条件、真值、假值。判断空值必须写 IS NULL；写 salary = NULL 永远不成立，会全部落到假值。

预期结果：4 行：小林/小周/小陈 已定薪；小李 未定薪

### 简单 CASE：按列的值等值分档

```sql
SELECT id, name,
       CASE dept_id
         WHEN 1 THEN '研发'
         WHEN 2 THEN '市场'
         ELSE '未分配'
       END AS 部门
FROM employee
ORDER BY id;
```

说明：四行：小林、小周「研发」，小陈「市场」，小李「未分配」（dept_id 是 NULL，两个 WHEN 都不等于）。简单 CASE 把列名写在 CASE 后面只写一次，后面只写要比较的值；它只能做等值判断，要写「salary >= 10000」这种范围就得换成搜索式 CASE WHEN 条件。

预期结果：4 行：研发、研发、市场、未分配

### 易错点

- 以为 IFNULL 也能处理空字符串：'' 不是 NULL，会原样返回，兜底不生效。
- 在 CASE 里写 salary = NULL 判断空值：与 NULL 比较得到未知，永远不成立，必须用 IS NULL。
- 把简单 CASE 当成万能：`CASE dept_id WHEN 1` 只能比值，写 `WHEN salary > 10000` 会变成「salary 是否等于 10000 的布尔值」，结果不对。

### 30 秒速记

IF 两选一；IFNULL 只认 NULL；NULLIF 相等转空；简单 CASE 等值分档、搜索 CASE 条件分档；先具体后宽泛。

### 自测

1. IFNULL(salary,0) 会不会把薪资为 0 的人也显示成 0？
   - 答：会显示 0，但那本来就是 0；它的问题是无法区分“原本是 0”和“原本是 NULL”，需要区分时用 CASE 单独判断 IS NULL。
2. CASE 的分支顺序为什么会影响结果？
   - 答：CASE 一旦命中就停止判断，范围宽的条件放前面会截走本应属于后面分支的行，所以更具体的条件要写在前面。

### 材料与来源

- [平台视频页] P30 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=30
- [官方文档] MySQL 8.0 Reference：流程控制函数 https://dev.mysql.com/doc/refman/8.0/en/flow-control-functions.html

证据说明：按 t40 帧核对：画面是 Workbench 里 IFNULL 的演示（对 'OK'、''、NULL 三种入参分别调用），并出现「case when then else end」的注释与地址分档需求；讲义 p59「3.4 流程函数」表格四行正是 IF / IFNULL / CASE WHEN 条件 / CASE 表达式 WHEN 值。正文示例为原创，使用自己的练习表，未照抄课堂表与数据；原声字幕未取得，讲师结论未核实。

## P31 31. 基础-函数-小结

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=31（p=31），时长 3:38
- 讲义：参照前节整理，无独立讲义正文；参照前节 3.1–3.4 函数（p53–61）

### 一句话理解

函数小结把四类常用函数列成一张清单：字符串、数值、日期、流程；按“参数顺序 + 返回什么”记，比背名字有用。

### 要点

- 四类清单：字符串（CONCAT、LOWER、UPPER、LPAD、RPAD、TRIM、SUBSTRING）、数值（CEIL、FLOOR、MOD、RAND、ROUND）、日期（CURDATE、CURTIME、NOW、YEAR、MONTH、DAY、DATE_ADD、DATEDIFF）、流程（IF、IFNULL、NULLIF、CASE）。
- 记“参数顺序 + 返回什么”：DATEDIFF 是前减后、SUBSTRING 位置从 1 起、ROUND 的第二位是小数位。
- 函数能套在列上：把 LPAD 写进 UPDATE 就能批量补工号（P27）；要对比结果时避开 RAND()、NOW()。

### 30 秒回忆

四类清单；参数顺序 + 返回值一起记；函数可以套列；测试避开 RAND/NOW。

关联课：P27 27. 基础-函数-字符串函数 · P29 29. 基础-函数-日期函数 · P30 30. 基础-函数-流程函数

### 自测

1. DATEDIFF 和 DATE_ADD 都带 DATE，怎么快速区分？
   - 答：DATE_ADD 是“日期 + 一段间隔”，返回新日期；DATEDIFF 是“两个日期差几天”，返回数字。

### 材料与来源

- [平台视频页] P31 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=31
- [官方文档] MySQL 8.0 Reference：字符串函数 https://dev.mysql.com/doc/refman/8.0/en/string-functions.html

证据说明：讲义无“小结”正文；本页参照前节 3.1–3.4 函数（p53–61）整理。画面 t40–t70 帧列出前三类函数，流程函数只写类别未列名，IF/IFNULL/CASE 由讲义 p59 表格与 P30 补全；原声字幕未取得。

## P32 32. 基础-约束-概述

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=32（p=32），时长 4:30
- 讲义：4.1 约束概述 p61–62（公开镜像；按主题对应，切点 notexact）

### 一句话理解

约束是写在表字段上的规则，用来挡住不该进库的数据；它由数据库替你检查。

### 步骤

1. 先分清目的：约束保证数据的正确、有效、完整，而不是替业务做全部判断。
2. 记六类：非空 NOT NULL、唯一 UNIQUE、主键 PRIMARY KEY、默认 DEFAULT、检查 CHECK、外键 FOREIGN KEY。
3. 决定写在哪：建表时直接写在字段后面，或建表后用 ALTER TABLE 补上。
4. 检查版本：CHECK 从 8.0.16 起才真正生效，更早版本会解析但忽略。

### SQL 示例

```
CREATE TABLE practice_score (
  id    INT PRIMARY KEY AUTO_INCREMENT,
  name  VARCHAR(10) NOT NULL UNIQUE,
  score INT CHECK (score >= 0 AND score <= 100),
  level CHAR(1) DEFAULT 'C'
) ENGINE = InnoDB;
```

说明：【原创演示】这是本笔记的原创建表演示，不依赖任何已有表，表名也与视频里的 user 表不同。四列各带一种约束：id 用主键 + 自增，name 非空且唯一，score 用 CHECK 限定 0–100，level 缺省为 'C'。本页原创例已在隔离的 MySQL 8.0.46 测试实例执行通过；你也可以用 p32.sql 在本地练习库跑一遍。

### 易错点

- 以为加了约束业务就安全：跨字段、跨表的规则（如“离职日期不早于入职”）约束管不全，仍需应用层校验。
- 在 8.0.16 之前的版本依赖 CHECK：语句能建成功却不拦数据，必须先确认版本。
- 不写约束名：后续要删除或修改时只能去查自动生成的名字。

### 30 秒速记

六类约束；目的是数据正确 / 有效 / 完整；可建表时写也可 ALTER 补；CHECK 需 8.0.16+。

### 自测

1. 约束能不能替代业务层的全部校验？
   - 答：不能。约束擅长单表单行、可声明的规则；涉及流程、跨表组合、外部系统的规则无法用约束表达，仍要应用逻辑。
2. 为什么示例里显式写 ENGINE=InnoDB？
   - 答：外键等特性依赖支持它的存储引擎，InnoDB 是 MySQL 8.0 默认且支持外键；写清楚可以避免换引擎后约束失效。

### 材料与来源

- [平台视频页] P32 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=32
- [官方文档] MySQL 8.0 Reference：CREATE TABLE https://dev.mysql.com/doc/refman/8.0/en/create-table.html

证据说明：按 t30 帧核对：概述页写明“约束是作用于表中字段上的规则，用于限制存储在表中的数据”“目的：保证数据库中数据的正确、有效性和完整性”，并列出六类约束表格，其中 CHECK 标注“8.0.16版本之后”。t012 帧的目录里出现“事务”一词，属课程目录，不作为本集讲解内容。正文示例表为原创，未使用视频里的 user 表；无字幕。

## P33 33. 基础-约束-演示

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=33（p=33），时长 15:00
- 讲义：4.2 约束演示 p62–64（公开镜像；按主题对应，切点 notexact）

### 一句话理解

这一集把常用约束写进一张表（主键、非空、唯一、默认、CHECK；外键在 P34 单独讲），再故意插入坏数据，看数据库怎样拒绝。

### 步骤

1. 先建带约束的表：主键自增、姓名非空且唯一、年龄用 CHECK 限定范围、状态给默认值。
2. 先插合法数据，确认能进；再一次只违反一条规则，观察报错。
3. 对着错误码读原因：1062 是唯一冲突、1048 是非空、3819 是 CHECK 不通过。
4. 改回合法值再插一次，确认表仍可正常写入。

### SQL 示例

```
CREATE TABLE practice_user (
  id     INT PRIMARY KEY AUTO_INCREMENT,
  name   VARCHAR(10) NOT NULL UNIQUE,
  age    INT CHECK (age > 0 AND age <= 120),
  status CHAR(1) DEFAULT '1'
) ENGINE = InnoDB;

INSERT INTO practice_user (name, age) VALUES ('小甲', 19), ('小乙', 25);
```

说明：【原创演示】原创演示表，字段与视频里的 user 表相似，但表名和数据都不同。插入只给 name 和 age，id 由自增生成，status 取默认 '1'。下面三种插入应被拒绝：重复 name 报 1062 Duplicate entry；name 传 NULL 报 1048；age 传 130 报 3819 检查约束失败。这些结果已在隔离的 MySQL 8.0.46 测试实例逐条验证（1062/1048/3819）；你也可以用 p33.sql 在本地练习库复现。

### 易错点

- 以为 UNIQUE 只能有一个 NULL：唯一约束允许多行 NULL，因为 NULL 之间不算相等。
- 以为自增编号会回填：删掉最大 id 再插新行，编号继续往后走，不补空位。
- 以为 CHECK 会顺带拦住 NULL：表达式结果为未知时按通过处理，要连 NULL 一起挡就得再写 IS NOT NULL。

### 30 秒速记

建表带约束 → 插合法数据 → 逐条违反 → 读 1062/1048/3819；UNIQUE 允许多 NULL；自增不回填。

### 自测

1. 为什么重复插入同名会失败，而插入两个 NULL 姓名不会？
   - 答：name 同时有 NOT NULL 和 UNIQUE：NULL 先被 NOT NULL 挡住；如果只有 UNIQUE，多个 NULL 是被允许的，因为唯一性比较不把 NULL 当作相等。
2. 报 3819 说明什么？
   - 答：CHECK 的表达式对这条数据不成立（结果为假），数据库拒绝了写入；先看表达式范围和传入值。

### 材料与来源

- [平台视频页] P33 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=33
- [官方文档] MySQL 8.0 Reference：CREATE TABLE https://dev.mysql.com/doc/refman/8.0/en/create-table.html

证据说明：按 t70 帧核对：画面是 Workbench 里执行建表 + 插入的演示，建表含主键自增、name 非空唯一、age 的 CHECK 范围、status 默认值，输出区出现 1062 Duplicate entry 报错。正文改用原创表名 practice_user 与自造数据，未照抄课堂表结构与姓名；无字幕，讲师逐步解说未核实。

## P34 34. 基础-约束-外键约束

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=34（p=34），时长 10:14
- 讲义：4.3.1 外键介绍 / 4.3.2 外键语法 p64–66（公开镜像；按主题对应，切点 notexact）

### 一句话理解

外键让子表的一列只能取父表里真实存在的值，从而不让“孤儿数据”进库。

### 跟播：视频里 ↔ 本站

视频用 emp（子表）与 dept（父表）讲外键，并说明这两张表在数据库层面其实没建约束。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| 讲义 dept：1 研发部、2 市场部（p65 原名） | 本页练习表同样用 1 研发部、2 市场部（沿用讲义原名） |
| 讲义 emp 6 行、含 job / entrydate | 本站 employee 4 行、5 列（没有 job / entrydate） |

看完应能：能说出子表父表方向，并知道插入不存在的父键会报 1452

P37 起换成本站另一套 6 列小表（2 号部门叫测试部），那是那一页的练习数据，不是这里写错。

### 步骤

1. 确定方向：employee.dept_id 指向 department.id，employee 是子表，department 是父表。没有外键时直接删父表一行，子表会留下指向不存在部门的孤儿数据，而且不报错——外键就是用来挡这个的。
2. 确认父表被引用的列有唯一索引（通常是主键），两边数据类型一致。
3. 建表时写 FOREIGN KEY (dept_id) REFERENCES department(id)，或建表后用 ALTER TABLE 补。
4. 插入一个父表不存在的 dept_id，确认被拒绝（错误 1452）。

### SQL 示例

```
ALTER TABLE employee
  ADD CONSTRAINT fk_employee_department
  FOREIGN KEY (dept_id) REFERENCES department (id);
```

说明：【原创演示】依赖两张练习表：department(id,name) 有 (1,'研发部')、(2,'市场部')；employee 就是前面那四行。加约束后，小林、小周、小陈的 dept_id 都能在父表找到；小李的 dept_id 是 NULL，外键不检查 NULL，所以也能留下。再插 dept_id=99 会报 1452 无法添加或更新子行。本页原创例已在隔离的 MySQL 8.0.46 测试实例执行通过（含被 1452 拒绝的那次插入）；也可以用 p34.sql 复现。

### 易错点

- 父表被引用列没有唯一索引：建外键直接失败（错误 1822），这不是数据问题而是索引问题。
- 子表里已经有对不上父表的旧数据：先加约束会失败，必须先清脏数据或补父表记录。
- 以为外键能挡所有错误值：NULL 不受外键检查，允许空就等于允许“没有部门”的行。

### 30 秒速记

子表引用父表唯一列；类型要一致；可建表时写也可 ALTER 补；NULL 不检查；违反报 1452。

### 自测

1. 为什么 department.id 必须是主键或唯一索引？
   - 答：外键要确认子表的值能唯一对应父表的一行；父表被引用列不唯一时无法判断指向哪条记录，数据库会直接拒绝建约束。
2. 小李 dept_id 为 NULL，加外键后会被删掉吗？
   - 答：不会。外键只校验非 NULL 的引用值，NULL 表示“没有引用”，可以存在于子表。

### 材料与来源

- [平台视频页] P34 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=34
- [官方文档] MySQL 8.0 Reference：FOREIGN KEY 约束 https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html

证据说明：按 t012/t020 帧核对：外键页给出概念“外键用来让两张表的数据之间建立连接，从而保证数据的一致性和完整性”，并用 emp（子表，dept_id）与 dept（父表，id）示意，页面明确注明“两张表在数据库层面并未建立外键关联”。正文沿用这个父子关系，但使用自己的练习表与数据，未声称与课堂 emp 表一致；无字幕。

## P35 35. 基础-约束-外键删除更新行为

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=35（p=35），时长 8:58
- 讲义：4.3.3 删除/更新行为 p67–68（公开镜像；按主题对应，切点 notexact）

### 一句话理解

父表记录被删或被改时子表怎么办，由 ON DELETE / ON UPDATE 后面的行为决定。

### 步骤

1. RESTRICT / NO ACTION：子表还有引用就拒绝删除或修改，这是默认的保守选择。
2. CASCADE：父表删一行，子表对应的行一起删；父表改主键，子表跟着改。
3. SET NULL：父表删一行，子表外键列置空，前提是该列允许 NULL。
4. SET DEFAULT：设计上置默认值，但 InnoDB 不支持，别在 MySQL 里指望它。

### SQL 示例

```
ALTER TABLE employee
  ADD CONSTRAINT fk_employee_department
  FOREIGN KEY (dept_id) REFERENCES department (id)
  ON DELETE SET NULL
  ON UPDATE CASCADE;
```

说明：【原创演示】仍是那两张练习表。设成 SET NULL 后删掉部门 2，小陈这一行的 dept_id 会变成 NULL，人还留在表里；如果改成 ON DELETE CASCADE，删部门 2 会连小陈一起删掉，这就是风险点。dept_id 必须可为空，否则 SET NULL 在删除时会报错。本页原创例已在隔离的 MySQL 8.0.46 测试实例执行通过；也可以用 p35.sql 复现。

### 易错点

- 为了“删起来方便”选 CASCADE：删一条父表记录可能连带删掉大量子表业务数据，且难恢复。
- 外键列是 NOT NULL 却设 SET NULL：删除父行时直接报错，约束和列定义要先对齐。
- 以为 RESTRICT 和 CASCADE 只是写法不同：一个拒绝操作、一个扩散删除，语义正好相反。

### 30 秒速记

RESTRICT/NO ACTION 拒绝、CASCADE 连动、SET NULL 置空（列须可空）、SET DEFAULT InnoDB 不支持。

### 自测

1. SET NULL 和 CASCADE 在“删部门”时分别发生什么？
   - 答：SET NULL 只把子表外键置空、员工行保留；CASCADE 会把引用该部门的员工行一起删掉，数据直接消失。
2. 为什么 RESTRICT 常被当作默认推荐？
   - 答：它让删除在存在引用时失败，迫使你先处理子数据，避免一次误删扩散到多张表。

### 材料与来源

- [平台视频页] P35 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=35
- [官方文档] MySQL 8.0 Reference：FOREIGN KEY 约束的参照操作 https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html

证据说明：按 t020/t030/t040/t070 帧核对：画面为“外键约束 删除/更新行为”表格，列出 NO ACTION（与 RESTRICT 一致）、RESTRICT、CASCADE、SET NULL（要求外键允许取 null）、SET DEFAULT（Innodb 不支持）。正文示例 DDL 为原创且未执行；无字幕，讲师现场演示未核实。

## P36 36. 基础-约束-小结

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=36（p=36），时长 2:15
- 讲义：参照前节整理，无独立讲义正文；参照前节 4.1–4.3 约束（p61–68）

### 一句话理解

约束小结就是六类约束的清单，加上“它保证什么、不能保证什么”。

### 要点

- 六类：NOT NULL、UNIQUE、PRIMARY KEY（可配 AUTO_INCREMENT）、DEFAULT、CHECK、FOREIGN KEY。
- 各自管什么：空值、重复、唯一标识、缺省值、取值范围、表间引用；主键 = 唯一 + 非空，UNIQUE 允许多个 NULL。
- 怎么加：建表时写在字段后，或用 `ALTER TABLE` 补；已有脏数据要先清洗，否则 ALTER 直接失败。

### 30 秒回忆

六类约束 + 各自作用 + 加法（建表 / ALTER）+ 先洗数据；主键 = 唯一 + 非空。

关联课：P32 32. 基础-约束-概述 · P33 33. 基础-约束-演示 · P34 34. 基础-约束-外键约束 · P35 35. 基础-约束-外键删除更新行为

### 自测

1. UNIQUE 和 PRIMARY KEY 最主要的区别是什么？
   - 答：主键要求非空且唯一，一张表只能有一个；UNIQUE 只保证不重复，允许 NULL，一张表可以有多个唯一约束。

### 材料与来源

- [平台视频页] P36 原视频页（BV1Kr4y1i7ru） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=36
- [官方文档] MySQL 8.0 Reference：CREATE TABLE 与约束 https://dev.mysql.com/doc/refman/8.0/en/create-table.html

证据说明：讲义无“小结”正文；本页参照前节 4.1–4.3 约束（p61–68）整理。画面 t80/tend 帧完整列出六类约束；原声字幕未取得，讲师逐句讲解未核。

## P37 37. 基础-多表查询-多表关系介绍

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=37（p=37），时长 12:12
- 讲义：5.1 多表关系（一对多 / 多对多 / 一对一） p69–72（公开镜像；按主题对应，切点 notexact）

### 一句话理解

表之间的关系就三种：一对一、一对多、多对多。判断方法很实在——看外键放在哪张表、要不要第三张中间表。

### 跟播：视频里 ↔ 本站

视频用 emp / dept 讲一对多、多对多、一对一三种关系，重点是外键放哪张表。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| 讲义 emp 17 行、8 列（含 job / entrydate / managerid） | 本站 employee 4 行、6 列（含 manager_id，没有 job / entrydate） |
| 讲义 dept 6 行：1 研发部、2 市场部、3 财务部… | 本站 department 3 行：1 研发部、2 测试部、3 运营部（2 号名字不同） |

看完应能：看到「一个部门多个员工」能立刻说出外键放 employee 这一侧

看视频时以讲义 emp/dept 为准，跟练时用本站 employee/department；表名与数字都不同，结构对应即可。

### 步骤

1. 先数“谁多”。一个部门有多名员工，一名员工只属于一个部门，所以 employee 是“多”的一方，外键 dept_id 放这里。为什么：外键放多的一方，每条员工记录只能填一个部门号，重复是天然的。
2. 一对一用于拆表。比如员工表和员工档案表，两边都只对应一条；外键放任意一方，但要加 UNIQUE，否则会退化成一堆一对多。
3. 多对多必须建中间表。学生选课，一个学生选多门课，一门课被多人选；中间表放 student_id、course_id 两个外键，联合起来当主键，重复选课才插不进去。
4. 关系定完再写查询。一对多靠 dept_id = id 连两张表；多对多要连三张表；一对一多一次 join。连接条件不是猜的，是外键指出来的。

### SQL 示例

```
-- 本页练习用的建表段：在 mysqlnotes_p37 里执行一次（后面各页用各自的 pNN.sql，不复用这里）
-- department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2

CREATE TABLE department (
  id INT PRIMARY KEY,
  name VARCHAR(20) NOT NULL
);
CREATE TABLE employee (
  id INT PRIMARY KEY,
  name VARCHAR(20) NOT NULL,
  age INT,
  dept_id INT,
  salary INT,
  manager_id INT
);
INSERT INTO department VALUES (1,'研发部'),(2,'测试部'),(3,'运营部');
INSERT INTO employee VALUES
 (1,'小林',22,1,8000,2),
 (2,'小周',25,1,10000,NULL),
 (3,'小陈',30,2,12000,2),
 (4,'小李',21,NULL,NULL,2);

-- 一对多：外键放在“多”的一方
ALTER TABLE employee
  ADD CONSTRAINT fk_emp_dept
  FOREIGN KEY (dept_id) REFERENCES department(id);
```

说明：前面几段只建表灌数据；这两张表属于本页练习库 mysqlnotes_p37，后面各页用的是各自的 pNN.sql，列数和数据类型可能不同，别拿这里的 6 列结构去套别的页。最后一段给 employee.dept_id 加外键：以后 dept_id 只能填 1、2、3 或 NULL，写 99 会被拒绝。自检跑 SELECT id,name,dept_id FROM employee ORDER BY id，应得 4 行，小李的 dept_id 是 NULL——外键允许空值，不代表他有部门。

### 易错点

- 外键方向写反（在 department 里放 employee_id），一个部门只能记一个员工，一对多就装不下了。
- 多对多只在一边加外键，另一边的对应关系无处存放，迟早出现重复行。
- 外键列类型必须和被引用主键一致（INT 对 INT），类型不同建约束会失败。

### 30 秒速记

一对多：外键在多的一方；一对一：外键+UNIQUE；多对多：中间表两个外键；连接条件来自外键。

### 自测

1. 一个部门多名员工，外键该放哪张表？
   - 答：employee（多的一方），存 dept_id 指向 department.id；放反了一个部门只能挂一个员工。
2. 多对多的中间表为什么常把两个外键做成联合主键？
   - 答：防止同一对组合重复插入，同时天然形成“两个一对多”。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P37（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=37
- [官方文档] MySQL 8.0 Reference Manual — JOIN Clause https://dev.mysql.com/doc/refman/8.0/en/join.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），依据平台标题、讲义 p69–72「5.1 多表关系」与公开静帧核对；多对多部分以 t≈292s 静帧（中间表）为准。一对一、一对多的表述为同主题通用整理。示例表与数据为本笔记自建，不是课程原表。

## P38 38. 基础-多表查询-概述

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=38（p=38），时长 8:04
- 讲义：5.2 多表查询概述（含 5.2.1 数据准备） p72–75（公开镜像；按主题对应，切点 notexact）

### 一句话理解

多表查询就是把两张表放在一起查。不写连接条件，MySQL 会把每行都和对方每行配一次，得到笛卡尔积——大部分行没有意义。

### 跟播：视频里 ↔ 本站

视频先跑 `select * from emp, dept`，把 17×6 的笛卡尔积摊开，再补 `where emp.dept_id = dept.id`。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| emp 17 行、dept 6 行 | employee 4 行、department 3 行（本站原创小表） |
| emp.dept_id / dept.id | employee.dept_id / department.id（关系相同） |

看完应能：能说出 4×3=12 行为什么出现、加哪一句能消掉无意义的组合

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 先看两张表各自的规模。4 名员工 × 3 个部门 = 12 行，这就是不加条件的全部组合；行数相乘是判断“有没有漏写条件”的最快信号。
2. 再用连接条件筛掉乱配的行。员工表的 dept_id 和部门表的 id 相等，才说明这名员工属于这个部门，12 行立刻剩 3 行。
3. 连接条件写在 WHERE（隐式）或 ON（显式）都行，效果一样；差别在可读性，以及外连接时条件必须放 ON。
4. 最后记住分类：内连接、外连接、自连接、子查询。前三个是“怎么把表拼起来”，子查询是“先算一个结果再用”。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p38.sql（独立练习库 mysqlnotes_p38，跑一次就有这两张表）

-- 故意不写连接条件：4 × 3 = 12 行
SELECT e.name AS 员工, d.name AS 部门
FROM employee e, department d;

-- 补上连接条件：只剩 3 行
SELECT e.name AS 员工, d.name AS 部门
FROM employee e, department d
WHERE e.dept_id = d.id;
```

说明：第一条返回 12 行，小林会分别配上研发部、测试部、运营部，明显是错配。第二条只剩 3 行：小林-研发部、小周-研发部、小陈-测试部。小李的 dept_id 是 NULL，NULL = 任何值都不成立，所以他不出现——这是内连接的正常行为，不是丢数据。

### 易错点

- 只写 FROM a, b 忘写 WHERE，结果行数突然变成两表行数之积；先数行数，再找条件。
- 想把无部门的员工也留下，却仍在用 WHERE 内连接，做不到；要改用 LEFT JOIN。
- 两表有同名列（都叫 name）时不用别名区分，会报 column ambiguous。

### 30 秒速记

多表=先组合再筛选；不写条件=笛卡尔积；连接条件把 4×3 变 3；内连接丢掉不匹配行。

### 自测

1. 4 行员工、3 行部门，不写连接条件返回几行？
   - 答：12 行，行数相乘；写了 dept_id = id 后是 3 行。
2. 小李为什么不出现在内连接结果里？
   - 答：他的 dept_id 是 NULL，与任何 id 比较都不为真，匹配不上；要保留他用 LEFT JOIN。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P38（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=38
- [官方文档] MySQL 8.0 Reference Manual — JOIN Clause https://dev.mysql.com/doc/refman/8.0/en/join.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），并以讲义公开镜像讲义 5.2 多表查询概述（p72–75）核对章节与主题；画面静帧只用于确认场景，不是内容边界。示例表与数据为本笔记自建，不是课程原表；不含原始帧或字幕。

## P39 39. 基础-多表查询-内连接

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=39（p=39），时长 8:15
- 讲义：5.3 内连接 p75–77（公开镜像；按主题对应，切点 notexact）

### 一句话理解

内连接只保留两张表能对上的行：员工有部门才出现，部门有员工才出现。

### 跟播：视频里 ↔ 本站

视频演示内连接的两种写法：`from emp, dept where …` 与 `inner join … on …`。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| emp.name / emp.age / dept.name | employee.name / employee.age / department.name |
| emp.dept_id = dept.id | employee.dept_id = department.id |

看完应能：两种写法都能写出来，并知道它们结果相同

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 先确定连接条件：employee.dept_id = department.id。这是“员工属于哪个部门”的唯一依据，条件写错会得到看似正常其实错位的结果。
2. 隐式写法把两张表用逗号并列，条件放 WHERE。老教材和简写常用，表一多就容易漏条件。
3. 显式写法用 INNER JOIN ... ON，连接条件和过滤条件分家。推荐这种：ON 只写“怎么对上”，WHERE 只写“要哪些行”，读起来不容易混。
4. 写完对一下行数。4 名员工里只有 3 人有部门，内连接就该是 3 行；多出来通常是漏了条件，少了则要查 dept_id 是否为空。另外，ON 里别写 e.age < 30 这类过滤——内连接结果一样，换成外连接就会保留不该保留的行。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p39.sql（独立练习库 mysqlnotes_p39，跑一次就有这两张表）

SELECT e.name AS 员工, e.age AS 年龄, d.name AS 部门
FROM employee e
INNER JOIN department d ON e.dept_id = d.id
ORDER BY e.id;
```

说明：返回 3 行：小林 22 研发部、小周 25 研发部、小陈 30 测试部。ON 负责把 dept_id 和 id 对上；ORDER BY e.id 只是让结果顺序稳定。把 INNER JOIN 换成“employee e, department d WHERE e.dept_id = d.id”，输出一模一样。 去掉 ORDER BY 再跑，行序可能变，但行集合不变：排序只影响展示。

### 易错点

- ON 写成 e.dept_id = d.name，类型和含义都错，却不一定报错，只会得到空结果或错配。
- 把过滤条件写进 ON 而不是 WHERE，在内连接里结果相同，但换成外连接含义就变了，别养成习惯。
- SELECT * 会带出两张表的全部列，同名列看起来重复；初学先显式写字段。
- 给表起了别名后，就不能再用原表名指字段：`SELECT e.name … FROM employee e` 里写 `employee.name` 会报 Unknown column。

### 30 秒速记

内连接=交集；隐式逗号+WHERE，显式 JOIN+ON；匹配不上的行直接消失。

### 自测

1. 内连接和外连接最根本的区别？
   - 答：内连接丢弃匹配不上的行；外连接保留某一侧全部行，另一侧用 NULL 补。
2. 两种内连接写法结果会不同吗？
   - 答：不会，结果相同，只是条件位置和可读性不同。
3. 内连接会不会返回重复行？
   - 答：会；一对多时一条左表记录匹配多行就会重复，需要按业务去重或聚合。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P39（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=39
- [官方文档] MySQL 8.0 Reference Manual — JOIN Clause https://dev.mysql.com/doc/refman/8.0/en/join.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），并以讲义公开镜像讲义 5.3 内连接（p75–77）核对章节与主题；画面静帧只用于确认场景，不是内容边界。示例表与数据为本笔记自建，不是课程原表；不含原始帧或字幕。

## P40 40. 基础-多表查询-外连接

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=40（p=40），时长 7:27
- 讲义：5.4 外连接 p77–78（公开镜像；按主题对应，切点 notexact）

### 一句话理解

外连接在内连接的基础上，保留某一侧“配不上”的行，另一侧补 NULL。

### 跟播：视频里 ↔ 本站

视频用左、右外连接演示「配不上的行怎么保留」。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| 讲义里没有部门的是陈友谅（dept_id 为空） | 本站是小李（dept_id 为空），左连接后部门列是 NULL |
| emp LEFT JOIN dept | employee LEFT JOIN department（写法相同） |

看完应能：能解释 LEFT JOIN 出来的 NULL 是「没配上」，不是数据坏了

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 先想清楚要保住谁。要“所有员工，包括没部门的”，员工在左就用 LEFT JOIN；要“所有部门，包括没人的”，部门放右边用 RIGHT JOIN。
2. ON 里写连接条件，也可以写“只影响能不能配上”的过滤。过滤放 ON 还是 WHERE，取决于你想保留哪些左行：想保留全部员工、只给研发部的人标上部门，就把 `d.name = '研发部'` 放 ON，其他员工照样出现、部门列是 NULL；只要研发部员工，就把条件放 WHERE（或者干脆用内连接）。两者是不同的问题，不是对错。判断办法：把条件从 ON 挪到 WHERE，如果连本来没配上的左行也一起消失，说明这个过滤本身在筛员工，不只是筛匹配。
3. 验证时数行数：左连接保证左表每一行至少出现一次，所以结果行数不会少于左表行数；本例 `department.id` 是主键、一名员工最多配上一个部门，结果才恰好 4 行。若左表一行配上多个右表行，行数变多（4 行变 5 行以上）是正常放大，不是写错；变成 3 行才说明有左行被过滤掉了。
4. 统计时要分清 COUNT(*) 和 COUNT(右表列)：前者数结果行，后者只数真正匹配上的行。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p40.sql（独立练习库 mysqlnotes_p40，跑一次就有这两张表）

SELECT e.name AS 员工, d.name AS 部门
FROM employee e
LEFT JOIN department d ON e.dept_id = d.id
ORDER BY e.id;

SELECT e.name AS 员工, d.name AS 部门
FROM employee e
RIGHT JOIN department d ON e.dept_id = d.id
ORDER BY d.id;
```

说明：左连接 4 行，第 4 行是“小李 / NULL”，因为他的 dept_id 为空；本例 department.id 是主键，一名员工最多配一个部门，所以没有一对多放大，行数才正好等于左表的 4 行。右连接换方向保部门，也是 4 行，最后一行“NULL / 运营部”，因为运营部还没人。想让运营部那行出现在“部门在左”的写法里，把表序换成 department LEFT JOIN employee 即可。 右连接也可以调换表序写成 LEFT JOIN，结果相同；团队里通常统一用 LEFT，方向更一致。

### 易错点

- LEFT JOIN 后在 WHERE 写 d.id IS NOT NULL 会滤掉 NULL 行，等价于内连接；要专门找“没部门的员工”应写 WHERE d.id IS NULL。
- COUNT(*) 数的是结果行（4），COUNT(d.id) 数的是匹配上的部门（3）；统计“多少人有部门”必须用后者。
- MySQL 没有 FULL OUTER JOIN；要两边都保留只能 LEFT UNION RIGHT，并注意去重。

### 30 秒速记

左保左、右保右；配不上补 NULL；过滤放 ON 还是 WHERE 看要保留哪些左行；COUNT(*)≠COUNT(右表列)。

### 自测

1. 左连接后想只留“没有部门的员工”，条件怎么写？
   - 答：WHERE d.id IS NULL（或 d.name IS NULL），写在连接之后。
2. LEFT JOIN 结果 4 行，COUNT(*) 和 COUNT(d.id) 分别是？
   - 答：4 和 3；COUNT(列) 不数 NULL。
3. 为什么 LEFT JOIN 之后再写 d.id IS NOT NULL 会变回内连接？
   - 答：它把补出来的 NULL 行全部滤掉，剩下的就是匹配行，结果与内连接相同。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P40（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=40
- [官方文档] MySQL 8.0 Reference Manual — JOIN Clause https://dev.mysql.com/doc/refman/8.0/en/join.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），并以讲义公开镜像讲义 5.4 外连接（p77–78）核对章节与主题；画面静帧只用于确认场景，不是内容边界。示例表与数据为本笔记自建，不是课程原表；不含原始帧或字幕。

## P41 41. 基础-多表查询-自连接

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=41（p=41），时长 8:30
- 讲义：5.5.1 自连接查询 p78–79（公开镜像；按主题对应，切点 notexact）

### 一句话理解

自连接就是一张表当两张用：给同一张表起两个别名，一个当员工、一个当上级，再按 manager_id = id 连起来。

### 跟播：视频里 ↔ 本站

视频用 managerid 把 emp 自己连自己，查「我的上级是谁」。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| emp.managerid → emp.id | employee.manager_id → employee.id |
| 讲义里张无忌的上级是金庸 | 本站小林、小陈的上级是 2 号小周 |

看完应能：能给同一张表起两个别名，并写出自连接条件

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 先给同一张表两个身份。e 代表“我”，m 代表“我的上级”；不给别名，MySQL 分不清你要哪一行。 别名起得有含义（e 员工、m 上级），比 a、b 好排查。
2. 连接条件是 e.manager_id = m.id，即“我的上级编号 = 对方的员工编号”。这跟普通两表连接没区别，只是两张表恰好同名。
3. 顶层老板没有上级，manager_id 是 NULL。要把他留在结果里就用 LEFT JOIN；用内连接他会消失，看起来像“少了个人”。
4. 字段一律带别名前缀。同名列（两张表都叫 name、id）不带前缀会报 ambiguous，带上就清楚了。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p41.sql（独立练习库 mysqlnotes_p41，跑一次就有这两张表）

SELECT e.name AS 员工, m.name AS 上级
FROM employee e
LEFT JOIN employee m ON e.manager_id = m.id
ORDER BY e.id;
```

说明：4 行：小林-小周、小周-NULL、小陈-小周、小李-小周。小周是老板，manager_id 为空，所以上级列是 NULL。把 LEFT 改成 INNER，只剩 3 行，小周整行不见——这是自连接最容易踩的坑。 把 ON 改成 e.id = m.manager_id，方向就反过来了，查的是“我的下属”，别名含义要跟着改。

### 易错点

- 两个别名写成同一个、或干脆不起别名，会报表名重复 / 字段 ambiguous。
- 用内连接查“所有员工及其上级”会漏掉没有上级的人；要全量必须 LEFT JOIN。
- 把 manager_id 理解成“部门编号”之类的别的字段，连接条件一错，结果会很整齐地错。

### 30 秒速记

自连接=同表两别名；e.manager_id=m.id；老板上级是 NULL；要全量用 LEFT JOIN。

### 自测

1. 自连接必须做什么？
   - 答：给同一张表起两个不同别名，并用别名区分字段。
2. 查“所有员工+上级”，内连接会少谁？
   - 答：少没有上级的人（如小周），因为 manager_id 为 NULL 匹配不上。
3. 自连接的两个别名可以互换吗？
   - 答：可以，但连接条件和字段含义要一起换；互换后查的是“我的下属”而不是“我的上级”。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P41（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=41
- [官方文档] MySQL 8.0 Reference Manual — JOIN Clause https://dev.mysql.com/doc/refman/8.0/en/join.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），并以讲义公开镜像讲义 5.5.1 自连接查询（p78–79）核对章节与主题；画面静帧只用于确认场景，不是内容边界。示例表与数据为本笔记自建，不是课程原表；不含原始帧或字幕。

## P42 42. 基础-多表查询-联合查询union

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=42（p=42），时长 5:48
- 讲义：5.5.2 联合查询 p79–80（公开镜像；按主题对应，切点 notexact）

### 一句话理解

连接横向拼列，UNION 纵向接行。UNION 去重，UNION ALL 不去重、更快。

### 跟播：视频里 ↔ 本站

视频用 UNION / UNION ALL 把两段查询纵向接起来，并说明去重差异。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| 讲义：工资 <5000 与 >20000 两段 UNION | 本站：同一写法换成 4 行小表的两段条件 |
| UNION 去重 / UNION ALL 保留重复 | 同左（语法与行为一致） |

看完应能：能说清 UNION 与 UNION ALL 差在去重，以及两段查询的列数、类型要对齐

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 先保证两条 SELECT 列数一样、对应列类型能相容。列数不同直接报错（ERROR 1222），类型不相容会隐式转换或丢精度。 比如 INT 和 VARCHAR 合并，MySQL 会先转成同一类型再比，结果可能出乎意料。
2. 要“合并后不重复”用 UNION：它把完全相同的整行去掉，代价是多一次去重。
3. 确定不会有重复、或就是要保留重复（比如两批统计各自的行）用 UNION ALL，省掉去重开销。
4. ORDER BY 写在最后一条 SELECT 之后，作用于整个合并结果；只给第一条写排序没有意义。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p42.sql（独立练习库 mysqlnotes_p42，跑一次就有这两张表）

-- 去重：小周只留一次
SELECT name FROM employee WHERE age >= 25
UNION
SELECT name FROM employee WHERE dept_id = 1;

-- 保留重复：小周出现两次
SELECT name FROM employee WHERE age >= 25
UNION ALL
SELECT name FROM employee WHERE dept_id = 1;
```

说明：第一段左边是小周、小陈，右边是小林、小周；UNION 去掉重复的小周，3 行。第二段同样的数据用 UNION ALL，4 行，小周出现两次。判断重复的依据是“整行完全相同”，不是只看某一列。 列名取第一条 SELECT 的别名，第二条写什么别名都不影响输出表头。

### 易错点

- UNION 只去“整行重复”；两行只要有一列不同就都保留。
- 两边列数不等报 ERROR 1222；用 SELECT * 时表结构一变就可能踩到，明确写列名。
- 拿 UNION 去拼含义不同的列（比如员工名接部门名），语义会乱；横向关联用 JOIN。
- UNION 的列名只认第一条 SELECT，第二条的别名不会生效，别指望它改表头。

### 30 秒速记

UNION 纵接行、去重；UNION ALL 保留重复；列数列型要对齐；ORDER BY 放最后。

### 自测

1. UNION 和 UNION ALL 的区别？
   - 答：UNION 对合并结果去重，UNION ALL 原样保留所有行、开销更小。
2. 两条 SELECT 列数不同会怎样？
   - 答：报 ERROR 1222，UNION 要求列数一致、类型相容。
3. 两条 SELECT 的列顺序不同能 UNION 吗？
   - 答：能执行，但会按位置硬拼，A 的姓名接到 B 的部门上，语义就错了；列顺序必须人工对齐。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P42（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=42
- [官方文档] MySQL 8.0 Reference Manual — UNION https://dev.mysql.com/doc/refman/8.0/en/union.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），并以讲义公开镜像讲义 5.5.2 联合查询（p79–80）核对章节与主题；画面静帧只用于确认场景，不是内容边界。示例表与数据为本笔记自建，不是课程原表；不含原始帧或字幕。

## P43 43. 基础-多表查询-子查询介绍

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=43（p=43），时长 2:30
- 讲义：5.6.1 子查询概述 p80–81（公开镜像；按主题对应，切点 notexact）

### 一句话理解

子查询就是 SELECT 里再套一个 SELECT：先算出内层的结果，再拿给外层当条件或当一列。

### 跟播：视频里 ↔ 本站

视频先给四类子查询的分类图（标量 / 列 / 行 / 表）。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| emp / dept 两张表 | employee / department 两张表 |
| 子查询可以出现在 WHERE、SELECT、FROM | 同左（位置与表的行数无关） |

看完应能：看到一个子查询能先判断它返回什么形状，再决定用什么运算符

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 先单独把内层跑一遍。看它返回“一个值、一列多行、一行多列、还是一张表”，这既是分类依据，也决定外层怎么接。
2. 再决定放哪里。WHERE 后面当条件最常见；SELECT 后面当一列（每行都带同一个值）；FROM 后面当临时表。 位置不同语义也不同：WHERE 里是过滤，SELECT 里是给每行加一列。
3. 最后检查外层能不能接得住。一个值可用 =、>；一列要用 IN；多列要用行比较；一张表必须放 FROM 并起别名。
4. 子查询不只能待在外层 SELECT 里，INSERT、UPDATE、DELETE 中同样可以嵌。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p43.sql（独立练习库 mysqlnotes_p43，跑一次就有这两张表）

SELECT name AS 员工, salary AS 工资,
       (SELECT AVG(salary) FROM employee) AS 公司平均
FROM employee
WHERE dept_id = 1;
```

说明：内层先算出全公司平均 10000（8000+10000+12000 除以 3，NULL 不参与）。外层查研发部两人，每行都带上这个 10000。预期 2 行：小林 8000 / 10000、小周 10000 / 10000。放在 SELECT 列表里的子查询必须是单值，返回多行会报错。 注意这个平均是整张表的，不随外层部门变化；要“本部门平均”，得把外层的 dept_id 传进内层，也就是后面的相关子查询。

### 易错点

- 内层返回多行却用 = 接，报 ERROR 1242（Subquery returns more than 1 row）。
- SELECT 列表里的子查询可能每行都算一次，数据量大时别滥用。
- 子查询不是越深越好；两层能解决就别套四层，可读性也是正确性的一部分。

### 30 秒速记

子查询=嵌套 SELECT；先看返回形状再选接法；WHERE/SELECT/FROM 三个位置；标量/列/行/表四类。

### 自测

1. 怎么判断该用标量还是列子查询？
   - 答：先单独跑内层看返回：一个值用标量比较，一列多行用 IN。
2. SELECT 后的子查询返回多行会怎样？
   - 答：报错，SELECT 列表位置要求单值。
3. 子查询放在 FROM 后面要注意什么？
   - 答：它会被当成派生表，MySQL 8 必须给别名，外层只能用别名引用它的列。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P43（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=43
- [官方文档] MySQL 8.0 Reference Manual — Subqueries https://dev.mysql.com/doc/refman/8.0/en/subqueries.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），并以讲义公开镜像讲义 5.6.1 子查询概述（p80–81）核对章节与主题；画面静帧只用于确认场景，不是内容边界。示例表与数据为本笔记自建，不是课程原表；不含原始帧或字幕。 分类（标量/列/行/表）按后续四集标题与本节定义整理。

## P44 44. 基础-多表查询-标量子查询

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=44（p=44），时长 5:53
- 讲义：5.6.2 标量子查询 p81–82（公开镜像；按主题对应，切点 notexact）

### 一句话理解

标量子查询返回一个值（一行一列），所以能直接放在 =、>、< 后面当普通数字用。

### 跟播：视频里 ↔ 本站

视频用「查询工资高于平均工资的员工」演示标量子查询。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| (select avg(salary) from emp) | (select avg(salary) from employee) |
| emp.salary 是 INT、17 行 | employee.salary 是 INT、4 行 |

看完应能：知道标量子查询只返回一个值，返回多行会报 1242

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 先确认内层只可能返回一行一列。按主键查、或用 AVG/MAX 这类聚合，天然只有一行；按普通字段查就可能翻车。
2. 把内层当成一个字面值来读。WHERE salary > (SELECT AVG(salary) ...) 就是“工资大于 10000”，理解上不需要特殊语法。
3. 跑之前先单独执行内层。看到 0 行或 2 行以上就要预警：0 行时内层得到 NULL，用 `=`、`>`、`<` 这类普通比较会得到 unknown，外层筛不出行；2 行以上直接报 1242。只有 `IS NULL`、`IS NOT NULL` 和 `<=>` 这类专门处理 NULL 的写法是例外——空集会被当成一个 NULL“值”来判，`WHERE (SELECT ...) IS NULL` 反而能查到行。
4. 写复杂条件时，先把内层结果抄成常量试跑外层，两次都对，组合起来基本不会错。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p44.sql（独立练习库 mysqlnotes_p44，跑一次就有这两张表）

-- 内层只有一个值：全公司平均工资
SELECT name AS 员工, salary AS 工资
FROM employee
WHERE salary > (SELECT AVG(salary) FROM employee);

-- 内层按 name 查，本数据里恰好一行；name 没有唯一约束，出现重名部门就会返回多行、外层报 1242
SELECT name AS 员工
FROM employee
WHERE dept_id = (SELECT id FROM department WHERE name = '测试部');
```

说明：第一段内层 AVG = 10000，外层只剩小陈 12000，1 行。第二段内层查出测试部 id = 2，外层得到小陈，1 行。如果把“测试部”改成不存在的名字，内层 0 行 → 返回 NULL → 外层一行都没有；这不是语法错，是逻辑上空了（这里用的是 `=`，所以空集筛不出行；换成 `IS NULL` 判断就是例外）。 排查顺序也简单：先单独跑内层看行数，再把值写成常量跑外层，问题一定出在其中一步。要让内层稳定只返回一行，就按主键查，或用 AVG/MAX 这类聚合。

### 易错点

- 内层 0 行不是报错，而是得到 NULL，外层条件静默落空；排查时先单独跑内层。
- 内层返回 2 行以上报 ERROR 1242，常见于按 name 查而 name 没有唯一约束。
- AVG/SUM 跳过 NULL，但 COUNT(*) 不跳过；算平均工资时别把没工资的人当成 0。

### 30 秒速记

标量=一行一列；能当普通值用；0 行→NULL，普通比较筛不出（IS NULL/<=> 例外）；多行→ERROR 1242。

### 自测

1. 标量子查询返回 0 行，外层 WHERE 会怎样？
   - 答：内层为 NULL，比较结果为 unknown，外层不返回该行，也不报错；只有 IS NULL / <=> 这类专门处理 NULL 的写法才会把它当值判。
2. 返回 3 行呢？
   - 答：报 ERROR 1242 Subquery returns more than 1 row。
3. 内层用了 GROUP BY 还能当标量用吗？
   - 答：通常不能，分组多半返回多行；标量位置只接受一行一列，多行报 1242。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P44（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=44
- [官方文档] MySQL 8.0 Reference Manual — Subqueries https://dev.mysql.com/doc/refman/8.0/en/subqueries.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），并以讲义公开镜像讲义 5.6.2 标量子查询（p81–82）核对章节与主题；画面静帧只用于确认场景，不是内容边界。示例表与数据为本笔记自建，不是课程原表；不含原始帧或字幕。 本集静帧可读文字很少，仅标题级；不据此声称讲师原话或原例。

## P45 45. 基础-多表查询-列子查询

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=45（p=45），时长 10:11
- 讲义：5.6.3 列子查询（IN / NOT IN / ANY / SOME / ALL） p82–83（公开镜像；按主题对应，切点 notexact）

### 一句话理解

列子查询返回一列多行，等于给你一个集合：在集合里挑值用 IN，拿一个值和整个集合逐个比用 ANY / ALL。

### 跟播：视频里 ↔ 本站

视频停在列子查询的操作符表（IN / NOT IN / ANY / SOME / ALL），并用研发部、财务部对照 > ANY 与 > ALL。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| emp.salary / emp.dept_id（17 行） | employee.salary / employee.dept_id（4 行） |
| 研发部 = dept_id 1（讲义 dept 6 行） | 研发部 = dept_id 1（本站 department 3 行） |

看完应能：看到 > any 能马上换成「大于最小值」，并知道 SOME 就是 ANY

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 先确认内层只查一列。多查一列就变成表子查询，IN 接不住；一列多行意味着结果可能是 0 行、1 行或很多行。
2. IN 判断「在不在这个集合里」，最常用；NOT IN 是它的反面，但集合里有 NULL 时整体变成 unknown，结果常常是 0 行。
3. ANY（与 SOME 完全同义）是「至少有一个满足」：研发部工资是 8000、10000，salary > ANY（那个子查询）只要大于 8000 就成立，也就是大于集合里的最小值。
4. ALL 是「全部都满足」：同一份数据里 salary > ALL（那个子查询）要大于 10000，也就是大于集合里的最大值。写完用行数反推，别只看语法。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p45.sql（独立练习库 mysqlnotes_p45，跑一次就有这两张表）

-- IN：研发部和测试部的员工
SELECT name AS 员工, dept_id AS 部门号
FROM employee
WHERE dept_id IN (
  SELECT id FROM department WHERE name IN ('研发部','测试部')
);

-- ALL：比研发部所有人都高（= 大于最大值）
SELECT name AS 员工, salary AS 工资
FROM employee
WHERE salary > ALL (
  SELECT salary FROM employee WHERE dept_id = 1
);

-- ANY：比研发部“任意一人”工资高即可（= 大于最小值）
SELECT name AS 员工, salary AS 工资
FROM employee
WHERE salary > ANY (
  SELECT salary FROM employee WHERE dept_id = 1
)
ORDER BY id;
```

说明：第一段内层返回 1、2 两个部门号，外层 3 行：小林、小周、小陈。第二段研发部工资是 8000、10000，> ALL 要大于 10000，只剩小陈 12000（1 行）。第三段 > ANY 只要大于 8000 就成立，小周 10000、小陈 12000 都入选（2 行）——同一份数据，ALL 与 ANY 的差别就是「全部」还是「任意一个」。记住方向：> ANY ≡ > MIN，> ALL ≡ > MAX；反过来 < ANY ≡ < MAX，< ALL ≡ < MIN。ALL 的好处是不用自己先想到 MAX，语义直接；ANY 的好处是「比其中任意一个高」这种需求能直说。

### 最小数列：3 个值讲清 ANY 与 ALL

```sql
-- 集合 {3000, 6000, 9000}：5000 比最小的大、比最大的小
SELECT 5000 > ANY (SELECT 3000 UNION SELECT 6000 UNION SELECT 9000) AS 比任意一个大,
       5000 > ALL (SELECT 3000 UNION SELECT 6000 UNION SELECT 9000) AS 比全部都大;
```

说明：结果一行：1、0。ANY 只要有一个成立就为真，门槛是最小值 3000；ALL 要求全都成立，门槛是最大值 9000。SOME 与 ANY 完全同义，写哪个都行。

预期结果：1、0（比任意一个大 = 1；比全部都大 = 0）

### 补充：空集与 NULL：两个反直觉结果（点开再看，不影响主线）

子查询为空集时：> ALL 恒为真（会返回外层所有行），> ANY 恒为假（0 行）。集合里有 NULL 时：> ALL 因为有一个 unknown 而永远不成立；> ANY 只要有一个非 NULL 比较成立就仍为真。下面这条在 MySQL 8.0.46 实测：all_empty=1、any_empty=0，两个与 NULL 比较的结果都是 unknown（IS NULL 为 1）。

```sql
SELECT (1 > ALL (SELECT salary FROM employee WHERE 1 = 0)) AS all_empty,
       (1 > ANY (SELECT salary FROM employee WHERE 1 = 0)) AS any_empty,
       ((5 > ALL (SELECT NULL)) IS NULL) AS all_null_unknown;
```

### 易错点

- NOT IN 的子查询结果含 NULL 时，整个条件永远不为真，结果 0 行；要么子查询加 WHERE dept_id IS NOT NULL，要么改用 NOT EXISTS。
- IN 后面的子查询只能返回一列；返回两列会报错，需要改成整行比较。
- 把 > ALL 读成「大于其中某一个」就全错了，那是 ANY 的语义；ALL 是「全部都要满足」。
- 以为 ANY 遇空集会全选：空集时 ANY 恒假（0 行），ALL 反而恒真。

### 30 秒速记

列子查询=一列多行；IN 在集合内；NOT IN 遇 NULL 全废；> ALL 等于大于最大值，> ANY 等于大于最小值；SOME 与 ANY 同义。

### 自测

1. NOT IN 子查询里有 NULL 会怎样？
   - 答：比较结果变 unknown，条件不成立，通常返回 0 行；先排除 NULL 或改 NOT EXISTS。
2. salary > ALL（子查询返回 8000、10000）等价于什么？
   - 答：salary > 10000，即大于集合最大值。
3. IN 和 = 该怎么选？
   - 答：子查询确定只返回一个值用 =；可能多行就用 IN，否则报 1242。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P45（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=45
- [官方文档] MySQL 8.0 Reference Manual — Subqueries https://dev.mysql.com/doc/refman/8.0/en/subqueries.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），依据平台标题、讲义 p82–83「5.6.3 列子查询」与公开静帧核对：讲义操作符表列 IN / NOT IN / ANY / SOME / ALL，并注明 SOME 与 ANY 等同；案例 C 就是「比研发部其中任意一人工资高」。示例表与数据为本笔记自建，不是课程原表。

## P46 46. 基础-多表查询-行子查询

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=46（p=46），时长 4:36
- 讲义：5.6.4 行子查询 p83–84（公开镜像；按主题对应，切点 notexact）

### 一句话理解

行子查询返回一行多列，用括号把几个字段凑成一行去比：(salary, manager_id) = (子查询)。

### 跟播：视频里 ↔ 本站

视频用「和张无忌薪资、直属领导都相同」演示行子查询。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| (salary, managerid) | (salary, manager_id) |
| emp 17 行 | employee 4 行 |

看完应能：会用括号把多列凑成一行去比较，并知道含 NULL 时整行比较会失效

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 先想清楚为什么要凑成一行。分成 salary = (...) AND manager_id = (...) 两个子查询，两个值可能来自不同的人，逻辑就错了；行比较保证同属一行。
2. 括号里的列顺序要一致。左边先 salary 后 manager_id，子查询也必须这个顺序；反了通常不报错，只是永远不相等。
3. 确认子查询只返回一行。行构造器配 = 时多行直接报 ERROR 1242；要多行匹配得换成 IN，那是下一页的表子查询。 行构造器不是万能元组比较，列数、类型都要严格对上。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p46.sql（独立练习库 mysqlnotes_p46，跑一次就有这两张表）

-- 与 3 号员工“工资 + 上级”都相同的人
SELECT id, name, salary, manager_id
FROM employee
WHERE (salary, manager_id) = (
  SELECT salary, manager_id FROM employee WHERE id = 3
);
```

说明：内层按主键查到一行 (12000, 2)，外层逐行比较两个值是否同时相等，只有小陈自己，1 行。把条件换成 id = 2 会得到 (10000, NULL)：NULL 参与行比较结果未知，连小周自己都匹配不上——这是行子查询最隐蔽的坑。 行比较的语义是“同时相等”，不是“任一相等”；要任一相等得拆成 OR，别硬套行构造器。

### 易错点

- 行里任一列为 NULL，整行比较为 unknown，永远不相等；要匹配 NULL 用 <=> 或拆开写 IS NULL。
- 左右列数或顺序不一致，可能不报错但结果恒空，先逐列对齐。
- 多行结果不能用 =，报 ERROR 1242，改用 IN。
- (a,b) = (SELECT a FROM ...) 列数不匹配会报 ERROR 1241，先数括号里的列。

### 30 秒速记

行子查询=一行多列；括号列序对齐；= 只接一行；NULL 让整行比较失效；多行换 IN。

### 自测

1. (salary, manager_id) = (子查询) 里子查询返回 2 行会怎样？
   - 答：报 ERROR 1242；多行比较要用 IN。
2. manager_id 为 NULL 的那行能被 = 匹配吗？
   - 答：不能，整行比较为 unknown；用 <=> 或分别处理 NULL。
3. 为什么不能把行子查询拆成两个独立子查询？
   - 答：两个子查询各自取值，可能来自不同行，拼出来的组合根本不存在。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P46（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=46
- [官方文档] MySQL 8.0 Reference Manual — Subqueries https://dev.mysql.com/doc/refman/8.0/en/subqueries.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），并以讲义公开镜像讲义 5.6.4 行子查询（p83–84）核对章节与主题；画面静帧只用于确认场景，不是内容边界。示例表与数据为本笔记自建，不是课程原表；不含原始帧或字幕。 题干只取“同薪资同上级”这一主题，示例与数据为自建。

## P47 47. 基础-多表查询-表子查询

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=47（p=47），时长 7:05
- 讲义：5.6.5 表子查询 p84–85（公开镜像；按主题对应，切点 notexact）

### 一句话理解

表子查询返回多行多列。整行匹配用 (列1,列2) IN (子查询)；放进 FROM 当派生表时，MySQL 8 要求必须起别名。

### 跟播：视频里 ↔ 本站

视频演示表子查询：整行 IN 匹配，以及把结果放进 FROM 当派生表。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| emp / dept | employee / department |
| 派生表必须起别名（如 e） | 同左（MySQL 8 强制要求） |

看完应能：能给派生表起别名，并说清派生表与 JOIN 的区别（一个当表、一个拼列）

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 先看内层是不是多行多列。多行多列不能用 =，也不能按单列思维写，要把外层对应的几列用括号打包，整行去比。
2. 列的顺序和数量要对齐。(dept_id, salary) IN (SELECT dept_id, MAX(salary) ...) 左边两列，内层也必须两列、同顺序。
3. 放进 FROM 时它就是派生表。外层只能看到它 SELECT 出来的列，而且必须写 AS t；MySQL 8 不给别名报 ERROR 1248。
4. 能改写就改写，但先确认等价：表子查询换成 JOIN + GROUP BY 之前，先想清楚会不会因为一对多把行数放大、会不会丢掉 NULL 分组；改完用同一份数据对一遍行数和结果。想比快慢就看实际执行计划（EXPLAIN），别凭“JOIN 一定更快”下结论——数据量、索引和优化器都会变。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p47.sql（独立练习库 mysqlnotes_p47，跑一次就有这两张表）

-- 每行是“本部门最高工资”，整行匹配
SELECT id, name, dept_id, salary
FROM employee
WHERE (dept_id, salary) IN (
  SELECT dept_id, MAX(salary)
  FROM employee
  GROUP BY dept_id
);

-- 同一类结果放进 FROM，就是派生表（必须起别名）
SELECT t.dept_id, t.avg_salary
FROM (
  SELECT dept_id, AVG(salary) AS avg_salary
  FROM employee
  WHERE dept_id IS NOT NULL
  GROUP BY dept_id
) AS t
WHERE t.avg_salary > 9000;
```

说明：第一段内层给出两个组合：(1,10000)、(2,12000)，还有一组 (NULL,NULL) 因为小李没部门；IN 匹配不上 NULL，所以外层 2 行：小周、小陈。第二段派生表先算平均，研发部 9000、测试部 12000，外层筛大于 9000，只剩 2 号部门 1 行。 两段的共同点只是“内层都返回多行多列”：第一段拿每部门最高工资做整行匹配，第二段拿每部门平均工资当派生表再筛，聚合不同、行集也不同，不能互换；相同的是“多行多列可以当集合用 IN，也可以当表放 FROM”这个用法区别。先判断你要筛行，还是要再算一层。

### 易错点

- 多行多列的子查询用 = 接，直接报 1242 或列数错误。
- 派生表不给别名，MySQL 8 报 ERROR 1248 Every derived table must have its own alias。
- 内层 SELECT * 再让外层引用，列名重复且含义不清；派生表只留需要的列并起清晰别名。

### 30 秒速记

表子查询=多行多列；整行 IN 匹配；也能当 FROM 派生表；派生表必须起别名。

### 自测

1. 表子查询和列子查询的区别？
   - 答：列子查询一列多行，用 IN 比单值；表子查询多行多列，要按整行匹配或当派生表。
2. 派生表在 MySQL 8 不给别名会怎样？
   - 答：报 ERROR 1248，每张派生表都必须有别名。
3. 派生表和临时表是一回事吗？
   - 答：不是；派生表只在当前查询内存在，查询结束就没了，也不是真实建表。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P47（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=47
- [官方文档] MySQL 8.0 Reference Manual — Subqueries https://dev.mysql.com/doc/refman/8.0/en/subqueries.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），并以讲义公开镜像讲义 5.6.5 表子查询（p84–85）核对章节与主题；画面静帧只用于确认场景，不是内容边界。示例表与数据为本笔记自建，不是课程原表；不含原始帧或字幕。 本课把多行多列子查询称表子查询，示例用整行 IN；FROM 派生表与别名要求为同主题补充说明。

## P48 48. 基础-多表查询-练习1

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=48（p=48），时长 17:42
- 讲义：5.7 多表查询案例（第 1–4 题所在段） p85–86（公开镜像；按主题对应，切点 notexact）

### 一句话理解

把前几页连起来用：先判断“要哪些行”，再选内连接还是外连接，最后检查有没有重复、有没有漏掉无部门的人。

### 跟播：视频里 ↔ 本站

视频在「5.7 多表查询案例」上逐题写：第 1 题隐式、第 2 题显式 + age<30、第 3 题去重、第 4 题外连接。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| 讲义 emp 17 行 / dept 6 行 | 本站 employee 4 行 / department 3 行（原创小表） |
| emp.job、emp.entrydate | 本页小表没有这两列，题目里也不问 |

看完应能：看到「显式内连接 + 年龄过滤」能立刻写出 INNER JOIN … ON … WHERE age < 30

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 逐题先写“表 + 连接条件 + 过滤条件”三行草稿。表是 employee 和 department，连接条件是 e.dept_id = d.id，过滤条件才决定内连接还是外连接。
2. 题目说“所有员工，没有部门也要”就选 LEFT JOIN；说“有员工的部门”就用内连接再去重。两类题的外连接方向相反，先抓关键词。
3. 写完数行数对答案：4 名员工里 3 人有部门，3 个部门里 2 个有人。题1 和题2 都是内连接，区别是题2 多一个年龄过滤，所以题2 行数更少（1 行对 3 行）；两题行集合不同，不能互相对照。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p48.sql（独立练习库，跑一次就有这两张表）

-- 题1：所有员工及其部门（隐式内连接），3 行
SELECT e.name AS 员工, e.age AS 年龄, d.name AS 部门
FROM employee e, department d
WHERE e.dept_id = d.id
ORDER BY e.id;

-- 题2：年龄小于 25 的员工及其部门（显式内连接；25 是本页自定，原课是 age < 30），1 行
SELECT e.name AS 员工, e.age AS 年龄, d.name AS 部门
FROM employee e
INNER JOIN department d ON e.dept_id = d.id
WHERE e.age < 25
ORDER BY e.id;

-- 题3：有员工的部门（内连接 + 去重）
SELECT DISTINCT d.id, d.name
FROM employee e, department d
WHERE e.dept_id = d.id;

-- 题4：所有员工及其部门，没部门也要出现
SELECT e.name AS 员工, e.age AS 年龄, d.name AS 部门
FROM employee e
LEFT JOIN department d ON e.dept_id = d.id
ORDER BY e.id;
```

说明：题1 是隐式内连接，4 名员工里只有 3 人有部门（小李 dept_id 为空，配不上），得到 3 行：小林、小周、小陈。题2 在本页是「显式内连接 + age < 25」：年龄小于 25 的有小林 22 和小李 21，小李没部门被内连接去掉，所以只剩 1 行小林——这就是题2 比题1 少的原因，两题行集合不同，不能互相对照。题3 得到 2 行：1 研发部、2 测试部；运营部没人所以不出现；DISTINCT 是因为研发部有两名员工，不去重会重复两行。题4 得到 4 行，小李的部门列是 NULL。 题2 里的 age < 25 是过滤员工，放在 WHERE；如果改成左连接再把它挪进 ON，年龄大的员工仍会出现，只是部门为 NULL，行数就多了。说明：题2 是本页自己的原创练习（显式内连接 + age < 25），不是视频原题逐字；原课第 2 题是「年龄小于 30 岁」+ 显式内连接（讲义 p85 第 2 题），第 1 题才是隐式连接且不带年龄条件，别混着对答案。

### 易错点

- 题3 忘了 DISTINCT，部门会按员工人数重复。
- 题4 “所有员工”却用了内连接，会漏掉小李且不报错。
- 把 e.age 之类的员工过滤写进 ON，左连接仍保留全部员工，含义容易混：连接条件放 ON，过滤员工放 WHERE。

### 30 秒速记

先草稿三行：表、连接条件、过滤；见“所有”用外连接；有员工的部门=内连接+去重；用行数对答案。

### 自测

1. 为什么题3 要 DISTINCT？
   - 答：研发部有两名员工，连接后部门行会重复；DISTINCT 按整行去重。
2. 题4 若把 LEFT 改成 INNER，结果几行？
   - 答：3 行，小李因为 dept_id 为 NULL 被丢掉。
3. 题4 结果里小李的部门列是什么？
   - 答：NULL；LEFT JOIN 保留了他，但 department 里没有对应行。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P48（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=48
- [官方文档] MySQL 8.0 Reference Manual — JOIN Clause https://dev.mysql.com/doc/refman/8.0/en/join.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），依据平台标题、讲义 p85–86「5.7 多表查询案例」与公开静帧核对，属主题级核实。示例表与数据为本笔记自建，不是课程原表；不含原始帧或字幕。 题干按同类题型重写；题2 用 age < 25 + 显式内连接，原课第 2 题是 age < 30 + 显式内连接，本页没有照抄原题。

## P49 49. 基础-多表查询-练习2

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=49（p=49），时长 21:29
- 讲义：5.7 多表查询案例（第 5–12 题所在段） p86–88（公开镜像；按主题对应，切点 notexact）

### 一句话理解

先把需求拆成单句验证：分组统计、相关子查询各一条；薪资档位用 salgrade 的区间连接，再把部门连上就是三表。

### 跟播：视频里 ↔ 本站

视频停在「所有员工的工资等级」与「研发部员工及工资等级」两题上，第二题就是三表。

| 视频里 / 原课字段 | 本站对应 |
|---|---|
| 讲义 emp 17 行 × dept 6 行 × salgrade 8 档 | 本站 employee 4 行 × department 3 行 × salgrade 4 档 |
| emp.salary BETWEEN salgrade.losal AND salgrade.hisal | employee.salary BETWEEN salgrade.losal AND salgrade.hisal |

看完应能：能先写出「工资落哪档」，再加一条部门连接变成三表

看视频时以讲义表为准，跟练时用本站表；结构相同、数字不同。

### 步骤

1. 统计类先定「以谁为基准」。要每个部门都出现（包括没人），就以 department 为左表 LEFT JOIN employee，别反过来。
2. 计数时用 COUNT(e.id) 而不是 COUNT(*)。左连接会给没人部门补一行 NULL，COUNT(*) 会把它算成 1 人，COUNT(e.id) 才是 0。
3. 相关子查询里外层的值会传进内层。e.salary > (SELECT AVG(x.salary) ... WHERE x.dept_id = e.dept_id) 读作「高于本部门平均」，内层每行重算。
4. 查薪资档次不用等值连接：`e.salary BETWEEN s.losal AND s.hisal` 就是一条非等值连接；要部门名就把 department 再连一次，变成三表。

### SQL 示例

```
-- 表：department(1 研发部, 2 测试部, 3 运营部)
-- employee：小林22/研发8000/上级2，小周25/研发10000/无上级，小陈30/测试12000/上级2，小李21/无部门/NULL/上级2
-- 建表与插入语句见本页练习脚本 p49.sql（独立练习库 mysqlnotes_p49，跑一次就有这两张表）

-- 每部门人数与平均工资（运营部也要出现）
SELECT d.name AS 部门, COUNT(e.id) AS 人数, AVG(e.salary) AS 平均工资
FROM department d
LEFT JOIN employee e ON e.dept_id = d.id
GROUP BY d.id, d.name
ORDER BY d.id;

-- 高于本部门平均工资的人（相关子查询）
SELECT e.name AS 员工, e.dept_id AS 部门号, e.salary AS 工资
FROM employee e
WHERE e.salary > (
  SELECT AVG(x.salary) FROM employee x WHERE x.dept_id = e.dept_id
);
```

说明：第一段 3 行：研发部 2 人 / 9000、测试部 1 人 / 12000、运营部 0 人 / NULL。第二段研发部平均 9000，小周 10000 入选；测试部只有小陈，12000 不大于 12000；小李没有部门，内层算不出平均（NULL），不入选。共 1 行。 反连接写法：SELECT d.id, d.name FROM department d LEFT JOIN employee e ON e.dept_id = d.id WHERE e.id IS NULL，结果只有 3 运营部。这三个数字就是核对后面所有统计题的基准，先记住它们。

### 第一步：工资落哪一档（salgrade 区间连接，两张表）

```sql
-- salgrade 是本页原创的 4 档小表（grade, losal, hisal），不是讲义那 8 档
SELECT e.name AS 员工, e.salary AS 工资, s.grade AS 档位
FROM employee e
JOIN salgrade s ON e.salary BETWEEN s.losal AND s.hisal
ORDER BY e.id;
```

说明：3 行：小林 8000→2 档、小周 10000→3 档、小陈 12000→3 档。BETWEEN 含两端，所以 12000 落在 9001–12000 的第 3 档。小李的 salary 是 NULL，NULL BETWEEN … 得 unknown，连不上任何档，这一行就消失了——非等值连接只保留「真的落进某个区间」的行。

预期结果：小林 8000 2｜小周 10000 3｜小陈 12000 3（小李 NULL 不出现）

### 第二步：再看是哪个部门（三表）

```sql
SELECT e.name AS 员工, d.name AS 部门, s.grade AS 档位
FROM employee e
JOIN department d ON e.dept_id = d.id
JOIN salgrade s ON e.salary BETWEEN s.losal AND s.hisal
ORDER BY e.id;
```

说明：3 行：小林 研发部 2、小周 研发部 3、小陈 测试部 3。两条连接条件互相独立：一条是等值（dept_id = id），一条是区间（BETWEEN losal AND hisal）。三表不是「一条 ON 写三个表」，而是每连一张表就补一条 ON，顺序按「先等值、后区间」读最清楚。

预期结果：小林 研发部 2｜小周 研发部 3｜小陈 测试部 3

### 易错点

- LEFT JOIN 后用 COUNT(*) 统计人数，会把「没人」的部门算成 1。
- 相关子查询忘写 x.dept_id = e.dept_id，就退化成全公司平均，结果看似合理但是错的。
- 区间连接漏了端点（写成 salary > losal AND salary < hisal），正好等于档位边界的工资会掉档。
- 以为 NULL 工资会落进第 1 档：NULL 参与 BETWEEN 得 unknown，不会匹配任何档。

### 30 秒速记

统计以保留侧为左表；COUNT(列) 才排除 NULL；相关子查询靠内外层字段挂钩；反连接用 LEFT JOIN + IS NULL。

### 自测

1. 运营部为什么会出现在结果里，人数却是 0？
   - 答：department 是左表，LEFT JOIN 保留全部部门；COUNT(e.id) 不数补出来的 NULL 行。
2. 相关子查询漏写 dept_id = e.dept_id 会怎样？
   - 答：变成和全公司平均比，结果错但通常不报错。
3. 统计每个部门人数为什么不用 COUNT(*)？
   - 答：LEFT JOIN 补出的 NULL 行也会被 COUNT(*) 计入，没人部门会变成 1。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P49（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=49
- [官方文档] MySQL 8.0 Reference Manual — Subqueries https://dev.mysql.com/doc/refman/8.0/en/subqueries.html

证据说明：本集无逐字稿（匿名 player/v2 返回 subtitles=[]、need_login_subtitle=true），依据平台标题、讲义 p86「5.7 多表查询案例」第 5–6 题与公开静帧核对：原课用 emp / dept / salgrade 三表，salgrade 是 grade, losal, hisal 三列共 8 档。本站用 employee / department 加一张 4 档原创 salgrade 复现同一形状，表名、行数与档位数字都不同，不能拿本站结果对讲义答案。

## P50 50. 基础-多表查询-小结

- 原视频：https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=50（p=50），时长 4:14
- 讲义：参照前节整理，无独立讲义正文；参照前节 5.1–5.7 多表查询（p69–88）

### 一句话理解

多表查询的决策只有三步：先看关系定连接条件，再看要不要保留配不上的行，最后看要不要嵌套。

### 要点

- 关系决定外键位置：一对多外键在多的一方；多对多要中间表；一对一外键加 UNIQUE。
- 连接先问“要不要保留全部”：只留匹配用内连接；保留一侧用左/右外连接；同表两个身份用自连接。
- 结果怎么合：合并行用 UNION（去重）或 UNION ALL；拿内层结果当条件或当表，就按返回形状选标量 / 列 / 行 / 表子查询。

### 30 秒回忆

关系 → 外键位置；连接 → 内/外/自；合并 → UNION；嵌套 → 四类子查询；先判断“保留谁”。

关联课：P37 37. 基础-多表查询-多表关系介绍 · P39 39. 基础-多表查询-内连接 · P43 43. 基础-多表查询-子查询介绍

### 自测

1. 一条查询里怎么同时体现外连接和计数正确？
   - 答：以保留侧为左表 LEFT JOIN，计数用 COUNT(右表主键)。
2. 多对多为什么不能只加一个外键？
   - 答：两边都是多，外键放哪边都会重复；要用中间表拆成两个一对多。

### 材料与来源

- [平台视频页] BV1Kr4y1i7ru P50（平台标题） https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=50
- [官方文档] MySQL 8.0 Reference Manual — JOIN Clause https://dev.mysql.com/doc/refman/8.0/en/join.html

证据说明：讲义无“小结”正文；本页参照前节 5.1–5.7 多表查询（p69–88）整理。原声字幕未取得（匿名接口字幕轨为空），讲师逐句讲解未核；本页不含 SQL 示例。
