第一章 查询语句
为方便本章节讲解,先建表并插入测试数据。
-- 创建员工表
CREATE TABLE employee (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
dept VARCHAR(20), -- 部门
salary INT, -- 薪资
hire_date DATE -- 入职日期
);
-- 插入测试数据
INSERT INTO employee (name, dept, salary, hire_date) VALUES
('张三', '技术部', 8000, '2022-01-10'),
('李四', '技术部', 9000, '2021-05-20'),
('王五', '市场部', 6000, '2023-02-15'),
('赵六', '市场部', 7000, '2022-08-01'),
('钱七', '人事部', 5000, '2023-03-12'),
('孙八', '技术部', 10000, '2020-11-30'),
('周九', '人事部', 5500, '2022-07-18');
第一节 条件(where)
一、基础认知
1. 作用
WHERE 用于筛选原始数据表中的行数据,从整张表里过滤出符合条件的数据,是 SQL 最核心的条件筛选子句。
2. 执行顺序(固定不变)
FROM 指定数据表 → WHERE 先行过滤数据 → GROUP BY 分组 → 聚合统计 → HAVING 过滤分组结果 → ORDER BY 排序 → LIMIT 限制行数
3. 核心区分
- WHERE :过滤分组前原始数据,越早过滤查询效率越高
- HAVING :过滤分组后聚合统计结果
4. 标准语法
SELECT 字段列表
FROM 表名
WHERE 筛选条件;
二、WHERE 全部可用筛选条件
1. 基础比较运算符
| 运算符 | 作用 |
|---|---|
| = | 等于 |
| != / <> | 不等于 |
| > | 大于 |
| < | 小于 |
| >= | 大于等于 |
| <= | 小于等于 |
示例
SELECT * FROM employee WHERE salary = 8000;
SELECT * FROM employee WHERE salary > 7000;
SELECT * FROM employee WHERE dept != '人事部';
2. 逻辑运算符(多条件组合)
AND:并且,所有条件同时满足OR:或者,满足任意一个条件即可NOT:取反,不符合该条件
优先级:NOT > AND > OR,复杂条件建议用()区分
-- 技术部 且 工资大于8000
SELECT * FROM employee WHERE dept='技术部' AND salary>8000;
-- 技术部 或 市场部
SELECT * FROM employee WHERE dept='技术部' OR dept='市场部';
-- 排除人事部
SELECT * FROM employee WHERE NOT dept='人事部';
3. 区间范围查询 BETWEEN AND
语法:字段 BETWEEN 最小值 AND 最大值包含边界两个数值
-- 工资6000~9000(包含6000、9000)
SELECT * FROM employee WHERE salary BETWEEN 6000 AND 9000;
4. 枚举匹配 IN / NOT IN
用于匹配多个固定值 ,简化多个OR写法
-- 匹配指定多个部门
SELECT * FROM employee WHERE dept IN ('技术部','市场部');
-- 排除指定部门
SELECT * FROM employee WHERE dept NOT IN ('人事部');
5. 空值判断
严禁使用 = NULL 判断空值
-
IS NULL:判断字段为空 -
IS NOT NULL:判断字段不为空SELECT * FROM employee WHERE salary IS NULL;
SELECT * FROM employee WHERE hire_date IS NOT NULL;
6. 模糊查询 LIKE
用于文本模糊匹配,搭配两个通配符
-
%:匹配任意长度任意字符 -
_:仅匹配单个字符-- 以孙开头
WHERE name LIKE '孙%';
-- 末尾为八
WHERE name LIKE '%八';
-- 名字包含三
WHERE name LIKE '%三%';
-- 第二个字为九
WHERE name LIKE '_九';
7. 日期时间条件
直接用日期字符串对比,也可搭配日期函数
WHERE hire_date >= '2022-01-01';
WHERE hire_date BETWEEN '2021-01-01' AND '2023-12-31';
WHERE YEAR(hire_date) = 2022;
8. 子查询对比 ANY / ALL
专门用来字段和子查询一组结果做对比
核心含义
ANY:满足子查询结果里任意一个条件即成立ALL:必须满足子查询结果里全部条件才成立SOME和ANY用法完全一致
等价替换(好记好用)
= ANY(子查询)等价IN(子查询)<> ALL(子查询)等价NOT IN(子查询)> ANY(子查询)大于集合最小值< ANY(子查询)小于集合最大值> ALL(子查询)大于集合最大值< ALL(子查询)小于集合最小值
实战示例
-- 工资大于市场部任意员工工资
SELECT * FROM employee WHERE salary > ANY(SELECT salary FROM employee WHERE dept='市场部');
-- 工资大于所有技术部员工工资
SELECT * FROM employee WHERE salary > ALL(SELECT salary FROM employee WHERE dept='技术部');
三、WHERE 绝对禁止使用的规则(高频易错)
- WHERE 中不能写聚合函数 聚合统计条件只能写在
HAVING中❌ 错误:WHERE COUNT(*)>2 - WHERE 不能使用字段别名 别名是查询后生成,WHERE 执行更早无法识别❌ 错误:
SELECT salary 工资 FROM employee WHERE 工资>7000✅ 正确:SELECT salary 工资 FROM employee WHERE salary>7000 - 字符串、日期类型数据必须加单引号,数字无需引号
- 空值只能用 IS NULL,不能用 = NULL
ANY/ALL必须搭配子查询使用,不能直接写固定数值
四、完整综合实战语句
整合所有前期知识点:WHERE 过滤 + 分组 + 聚合 + HAVING + 排序 + 限制行数
SELECT dept 部门,COUNT(*) 人数,AVG(salary) 平均薪资
FROM employee
-- WHERE 提前过滤原始数据
WHERE salary >= 5000 AND hire_date > '2021-01-01'
GROUP BY dept
-- HAVING 过滤分组统计结果
HAVING AVG(salary) > 6000
ORDER BY 平均薪资 DESC
LIMIT 0,3;
五、极简总结
WHERE核心:筛选原始行数据,优先过滤,提升查询性能- 常用条件全覆盖:比较符、逻辑符、区间、枚举、空值、模糊、日期、ANY/ALL 子查询对比
- 硬性禁忌:不用聚合函数、不用字段别名、空值专用判断语法
- 区分边界:行筛选用
WHERE,组筛选用HAVING
第二节 分组聚合
一、核心语法
1.语法
SELECT
分组字段, -- 必须写在 GROUP BY 里
聚合函数(字段) -- 对每组数据做统计
FROM 表名
WHERE 条件 -- 分组前过滤数据(先筛选,再分组)
GROUP BY 分组字段 -- 核心:按哪个/哪些字段分组
HAVING 聚合条件; -- 分组后过滤(筛选分组结果)
2.常用聚合函数(分组后必用)
| 函数 | 作用 |
|---|---|
COUNT() |
统计行数 / 非空值数量 |
SUM() |
求和 |
AVG() |
求平均值 |
MAX() |
取最大值 |
MIN() |
取最小值 |
二、示例
1.统计每个部门人数
SELECT
dept AS 部门, -- 分组字段
COUNT(*) AS 员工数 -- 聚合函数:统计每组行数
FROM employee
GROUP BY dept; -- 按部门分组
2.查询结果
| 部门 | 员工数 |
|---|---|
| 技术部 | 3 |
| 市场部 | 2 |
| 人事部 | 2 |
三、注意事项
1.聚合函数的使用位置限制
聚合函数(如COUNT()、SUM()、AVG()、MAX()、MIN())只能出现在:
SELECT查询的列(要统计的字段)ORDER BY子句(对聚合结果排序)HAVING子句(对分组后的聚合结果过滤)
不能出现在:
WHERE子句:WHERE是在分组前过滤原始数据,此时聚合结果还没计算出来GROUP BY子句:GROUP BY是用来指定分组字段,不能直接写聚合函数
举个反例(错误写法):
-- 错误:WHERE 中不能用聚合函数 COUNT()
SELECT dept, COUNT(*)
FROM employee
WHERE COUNT(*) > 2
GROUP BY dept;
正确写法(用HAVING):
SELECT dept, COUNT(*)
FROM employee
GROUP BY dept
HAVING COUNT(*) > 2;
2.聚合函数对NULL的处理规则
COUNT(*):会统计所有行数 ,包含列值为NULL的行- 其他聚合函数(
COUNT(列名)、SUM()、AVG()、MAX()、MIN()):会忽略列值为 NULL 的行,不参与统计
举个例子:假设表score有字段math,数据为[80, 90, NULL, 70]
COUNT(*)→ 结果为4(统计所有行)COUNT(math)→ 结果为3(忽略 NULL)AVG(math)→ 计算(80+90+70)/3 = 80(不把 NULL 算入)
第三节 排序(order by)
一、核心语法与规则
1. 基础语法说明
SELECT 字段列表
FROM 表名
ORDER BY 排序字段1 [排序规则], 排序字段2 [排序规则];
ASC:升序排序(从小到大),默认规则,可省略不写DESC:降序排序(从大到小),必须显式指定
2. 完整语法位置(必须遵守顺序)
ORDER BY 子句在查询中必须写在最后,完整执行顺序:
SELECT 字段列表
FROM 表名
[WHERE 条件]
[GROUP BY 分组字段]
[HAVING 分组后条件]
ORDER BY 排序字段 [ASC/DESC]; -- 排序子句,永远在最后
二、实战示例(用之前的员工表)
还是用我们之前的 employee 表来演示。
示例 1:单字段排序(按薪资升序 / 降序)
-- 按薪资升序(默认ASC,从小到大)
SELECT name, dept, salary
FROM employee
ORDER BY salary; -- 等价于 ORDER BY salary ASC;
-- 按薪资降序(从大到小)
SELECT name, dept, salary
FROM employee
ORDER BY salary DESC;
示例 2:多字段排序(先按部门,再按薪资降序)
多字段排序的逻辑:先按第一个字段排序,当第一个字段值相同时,再按第二个字段排序。
-- 先按部门升序,同部门内再按薪资降序
SELECT dept, name, salary
FROM employee
ORDER BY dept ASC, salary DESC;
执行效果:
- 先按部门排序(人事部、市场部、技术部)
- 每个部门内部,薪资高的排在前面
三、排序的关键规则与注意事项
1. 排序对 NULL 值的处理
不同数据库对 NULL 的排序规则略有差异,MySQL 的规则是:
- 升序(ASC):
NULL值会排在最前面(相当于比所有值都小) - 降序(DESC):
NULL值会排在最后面(相当于比所有值都小)
举个例子:如果有薪资为 NULL 的员工
SELECT name, salary FROM employee ORDER BY salary;
结果中,薪资为 NULL 的行会排在最前面,后面再按实际薪资升序排列。
2. ORDER BY 中可以用的内容
✅ 可以使用:
- 表中的字段名
- 聚合函数(如
SUM(salary)) - 字段别名(如
部门总薪资)
❌ 不可以使用:
- 子查询、复杂表达式中未在 SELECT 中出现的字段(部分场景除外,不推荐)
3. 常见错误写法
-- 错误:ORDER BY 写在了 GROUP BY 前面,违反语法顺序
SELECT dept, SUM(salary)
FROM employee
ORDER BY SUM(salary) DESC
GROUP BY dept;
-- 错误:用 WHERE 过滤聚合结果,和排序无关但属于高频错误
SELECT dept, SUM(salary)
FROM employee
WHERE SUM(salary) > 15000 -- 聚合过滤必须用 HAVING
GROUP BY dept
ORDER BY SUM(salary) DESC;
第四节 LIMIT
LIMIT 关键字的本质: LIMIT 的核心本义是「限制、限定」 ,它的作用是限制 SQL 查询结果的返回行数,而非专门为分页设计,分页只是它最常用的应用场景。
一、基础语法
-- 语法1:双参数(限制返回指定区间的N条)
SELECT 字段列表
FROM 表名
[WHERE 条件]
[GROUP BY 分组字段]
[HAVING 分组后条件]
[ORDER BY 排序字段]
LIMIT M, N;
-- 语法2:单参数(限制返回前N条,这个仅仅是特例,不建议记忆)
SELECT 字段列表
FROM 表名
[WHERE 条件]
[GROUP BY 分组字段]
[HAVING 分组后条件]
[ORDER BY 排序字段]
LIMIT N;
N:本次查询要返回的数据条数M:偏移量(跳过前面 M 条数据,从 0 开始计数)- 执行顺序:
WHERE → GROUP BY → HAVING → ORDER BY → LIMIT(LIMIT 永远在最后)
二、两种语法的逻辑与示例
1. 双参数 LIMIT M, N
逻辑 :限制只返回跳过 M 条之后的 N 条,是分页场景的核心用法。
- 分页对应公式:如果每页显示
pageSize条,当前是第pageNo页(页码从 1 开始)- 偏移量
M = (pageNo - 1) * pageSize - 返回条数
N = pageSize
- 偏移量
示例 1:标准分页(每页 2 条,第 2 页)
-- 第2页 → M=(2-1)*2=2,N=2
SELECT name, dept, salary
FROM employee
ORDER BY id -- 必须排序,保证数据顺序稳定
LIMIT 2, 2;
返回结果:
| name | dept | salary |
|---|---|---|
| 赵六 | 市场部 | 7000 |
| 钱七 | 人事部 | 5000 |
2. 单参数 LIMIT N
逻辑 :限制只返回结果集中最前面的 N 条,和分页无关,只是单纯限制行数。
-- 取薪资最高的3名员工(非分页场景)
SELECT name, dept, salary
FROM employee
ORDER BY salary DESC
LIMIT 3;
返回结果:
| name | dept | salary |
|---|---|---|
| 孙八 | 技术部 | 10000 |
| 李四 | 技术部 | 9000 |
| 张三 | 技术部 | 8000 |
三、分页场景的关键注意事项
1. 分页必须搭配ORDER BY
MySQL 默认数据存储顺序不确定,不加排序会导致:
-
同一页两次查询结果不同
-
翻页时出现重复 / 漏数据✅ 推荐:用主键 / 唯一索引排序,保证顺序稳定
SELECT * FROM employee ORDER BY id LIMIT 10, 10;
2. 大偏移量性能问题与优化
当偏移量很大时(如LIMIT 100000, 10),MySQL 会扫描前 100010 条数据再丢弃前 100000 条,性能极差。
优化方案:
-
方案 1(推荐):游标分页(基于主键)
-- 假设上一页最后一条id为100000,直接从该id后取10条 SELECT * FROM employee WHERE id > 100000 ORDER BY id LIMIT 10; -
方案 2:覆盖索引优化
SELECT e.* FROM employee e INNER JOIN ( SELECT id FROM employee ORDER BY id LIMIT 100000, 10 ) t ON e.id = t.id;
3. 分页总条数计算
前端分页需要总数据量来计算总页数,用COUNT(*)查询总数:
-- 1. 查询总条数
SELECT COUNT(*) AS total FROM employee WHERE hire_date >= '2022-01-01';
-- 2. 计算总页数:总页数 = CEIL(总条数 / 每页条数)
-- 例:总条数7,每页2条 → 总页数=4
-- 3. 执行分页查询
SELECT * FROM employee WHERE hire_date >= '2022-01-01' ORDER BY id LIMIT 0, 2;
4. NULL 值对排序分页的影响
MySQL 中NULL值排序规则:
- 升序(
ASC):NULL排在最前面 - 降序(
DESC):NULL排在最后面分页时需注意NULL值对结果顺序的影响。
四、LIMIT 与聚合函数的配合规则
结合之前聚合函数的知识点,补充 LIMIT 的使用限制:
- 聚合函数不能出现在
WHERE/GROUP BY中,但可以在ORDER BY/HAVING中,再配合 LIMIT - 除
COUNT(*)外,其他聚合函数(含COUNT(列名))会忽略NULL值,不影响 LIMIT 的行数限制
五、核心总结
- 本质定位 :
LIMIT是限制结果行数的子句,分页是其高频应用场景,而非本身就是分页 - 语法逻辑 :
LIMIT N:取前 N 条LIMIT M, N:跳过 M 条,取 N 条(分页公式:M=(pageNo-1)*pageSize)
- 分页关键 :必须加
ORDER BY保证顺序稳定,大偏移量用游标分页优化 - 执行顺序 :
WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
第五节 总结
一、完整固定书写顺序(顺序绝对不能乱)
SELECT 字段列表
FROM 表名
[WHERE 行数据过滤条件]
[GROUP BY 分组字段]
[HAVING 分组后聚合条件]
[ORDER BY 排序字段]
[LIMIT 偏移量, 条数];
二、执行顺序(底层真实执行顺序)
- FROM 确定查询哪张表
- WHERE 过滤原始数据(先删不需要的数据)
- GROUP BY 进行数据分组
- 聚合函数 统计计算(sum count avg max min)
- HAVING 过滤分组之后的统计结果
- SELECT 确定最终显示哪些字段、起别名
- ORDER BY 对结果排序
- LIMIT 限制最终返回数据行数
三、每一句关键字作用 + 用法
1. SELECT 查什么
- 查询字段、常量、聚合函数
- 支持 AS 起别名,
AS可省略 - 别名只能在 ORDER BY、HAVING 使用
- 不能在 WHERE 使用别名
2. FROM 从哪查
- 指定数据表
- 表也可以用 AS 起别名
- 子查询当表使用必须起别名
3. WHERE 行条件过滤(重点最全)
作用:分组之前筛选原始数据支持所有条件:
- 比较运算符:
= != > < >= <= - 逻辑运算符:
AND 并且、OR 或者、NOT 取反 - 区间:
BETWEEN 值1 AND 值2(包含两端) - 多选匹配:
IN() 、NOT IN() - 空值判断:
IS NULL 、IS NOT NULL - 模糊查询:
LIKE % _ - 子查询对比:
ANY 任意满足、ALL 全部满足
WHERE 三大禁令
- 不能写聚合函数
- 不能使用字段别名
- NULL 不能用 = 判断
4. GROUP BY 分组
- 按照指定字段进行分组
- SELECT 后只能写:分组字段 + 聚合函数
5. HAVING 分组后过滤
- 只过滤聚合统计结果
- 专门用来放聚合函数条件
- 可以使用字段别名
- 和 WHERE 区别:
- WHERE:分组前过滤行
- HAVING:分组后过滤组
6. ORDER BY 排序
ASC升序(默认可省)DESC降序- 支持多字段排序
- 支持别名、聚合函数排序
- 分页必须搭配 ORDER BY 保证数据不乱
7. LIMIT 限制行数
- 本意:限制查询结果条数,分页只是常用场景
- 格式 1:
LIMIT N取前 N 条 - 格式 2:
LIMIT 起始下标, 条数 - 分页公式:
(页码-1)*每页条数 - 永远写在整条 SQL最后
四、最全语法精简口诀
先查表,再筛行 分完组,算聚合 组后筛,定字段 排好序,限条数
行过滤用 WHERE,组过滤用 HAVING 别名查询之后出,WHERE 里面不能用 聚合函数放后面,HAVING 里做判断 排序写完加限制,LIMIT 放最末尾
五、一条完整版万能模板
-- 万能标准查询模板
SELECT 字段1,字段2,聚合函数(字段) AS 别名
FROM 表名
WHERE 普通查询条件
GROUP BY 分组字段
HAVING 聚合函数条件
ORDER BY 排序字段 DESC
LIMIT 0,10;
第六节 关键字as(了解)
一、单词本意
as 本意:作为、当作、起名为 作用:给字段、表、查询结果起别名,简化书写、美化展示、方便调用。
二、整体规则
- AS 可以省略不写,空格直接代替
- 别名建议尽量不用中文,如需中文加反引号 `````
- 别名只在当前这条 SQL 内生效,数据库表里不会改变原名
- 别名不能在 WHERE 里直接使用 ,可以在
GROUP BY / HAVING / ORDER BY使用
三、三大常用用法(由浅到深)
用法 1:给查询字段起别名(最常用)
语法
字段名 AS 别名
AS 可省略
字段名 别名
示例
-- 原名太长,起简短别名
SELECT name AS 姓名, salary AS 工资 FROM employee;
-- 省略 AS 写法(日常最常用)
SELECT name 姓名, salary 工资 FROM employee;
-- 聚合函数起别名
SELECT dept 部门, COUNT(*) 人数, SUM(salary) 总薪资
FROM employee
GROUP BY dept;
好处
-
查询结果表头好看
-
后面排序、分组直接用别名,不用重复写长表达式
-- 直接用别名排序
SELECT name, salary 工资 FROM employee ORDER BY 工资 DESC;
用法 2:给数据表起别名(多表联查必备)
语法
表名 AS 表别名
同样可省略 AS
示例
-- 简写表名,简化SQL
SELECT e.name, e.salary
FROM employee AS e;
-- 省略 AS
SELECT e.name, e.salary
FROM employee e;
多表联查必用
SELECT e.name, d.dept_name
FROM employee e
JOIN dept d ON e.dept_id = d.id;
好处:SQL 更简短,结构清晰。
用法 3:给子查询结果集起别名(派生表必须加 AS)
重点硬性规则: MySQL 里 临时查询结果当作表使用时,必须用 AS 起别名,缺一不可
错误写法
SELECT * FROM (SELECT name,salary FROM employee);
-- 报错:每个派生表必须有自己的别名
正确写法
SELECT * FROM (
SELECT name,salary FROM employee
) AS temp; -- temp 就是临时结果集别名
四、AS 使用禁区 & 易错点
1. WHERE 不能使用字段别名
-- 错误
SELECT salary 工资 FROM employee WHERE 工资 > 7000;
-- 正确:只能用原字段名
SELECT salary 工资 FROM employee WHERE salary > 7000;
原因:WHERE 执行早于别名生成
2. HAVING / ORDER BY 可以用别名
SELECT dept 部门, AVG(salary) 平均工资
FROM employee
GROUP BY dept
HAVING 平均工资 > 6000 -- 可以用别名
ORDER BY 平均工资; -- 可以用别名
3. 别名带空格、中文特殊字符,加反引号
SELECT salary AS `员工薪资` FROM employee;
4. AS 只改显示名,不改库表真实字段名
AS 只是查询展示别名 ,不会修改数据库里真实字段名。修改真实字段名用 ALTER TABLE,和 AS 无关。
五、极简总结背诵版
- as = 起别名,可写可省略
- 三大用途:字段别名、表别名、子查询别名
- 子查询结果集必须加 as 别名,语法强制要求
- 别名WHERE 不能用 ,
GROUP BY、HAVING、ORDER BY都能用 - 只改查询显示,不改数据库真实名字
第七节 DISTINCT 和 ALL
一、DISTINCT(去重)
意思
distinct = 不同的、唯一的
作用
去掉查询结果里重复的行,只保留唯一值
语法
SELECT DISTINCT 字段名 FROM 表名;
例子
员工表里部门有重复:技术部、技术部、市场部、市场部、人事部
SELECT DISTINCT dept FROM employee;
结果只会出现:技术部、市场部、人事部
一句话总结
DISTINCT = 去重,只显示不重复的数据
二、ALL(全部,默认就是它)
意思
all = 全部、所有
作用
返回所有数据,包括重复的
语法
SELECT ALL 字段名 FROM 表名;
重点
MySQL 默认就是 ALL,所以平时根本不用写
下面两句效果完全一样:
SELECT name FROM employee;
SELECT ALL name FROM employee;
一句话总结
ALL = 返回全部数据(包括重复),默认就是 ALL,可省略
三、最简单对比(一眼记住)
| 关键字 | 作用 | 是否需要写 |
|---|---|---|
| DISTINCT | 去重,只显示唯一值 | 需要写才生效 |
| ALL | 显示全部(含重复) | 默认就是,不用写 |
四、最精简记忆口诀
- DISTINCT:去重
- ALL:全部(默认)