SQL基础-条件查询与分组查询
最近复习 SQL 的时候,发现条件查询(WHERE)和分组查询(GROUP BY + HAVING)这两块内容很容易混淆,尤其是 WHERE 和 HAVING 的区别。再加上 CASE WHEN 在实际开发中几乎无处不在,所以把这些知识点整理到一起,方便对比学习。
本文使用以下示例表来演示所有查询:
1 | -- 员工表 |
一、条件查询(WHERE)
WHERE 子句用于在查询时过滤行,只返回满足条件的记录。它在数据分组(GROUP BY)之前执行。
基本语法:
1 | SELECT 列名 FROM 表名 WHERE 条件; |
1. 比较运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
= |
等于 | WHERE age = 28 |
!= 或 <> |
不等于 | WHERE department != '技术部' |
> |
大于 | WHERE salary > 15000 |
< |
小于 | WHERE age < 30 |
>= |
大于等于 | WHERE salary >= 12000 |
<= |
小于等于 | WHERE age <= 25 |
1 | -- 查询工资大于 15000 的员工 |
2. BETWEEN...AND
用于筛选某个范围内的值(包含两端边界值)。
1 | -- 查询工资在 10000 到 18000 之间的员工(包含 10000 和 18000) |
⚠️ 注意:
BETWEEN...AND是闭区间,即包含边界值。并且小值必须写在前面,BETWEEN 18000 AND 10000查不到任何结果。
也可以用于日期范围:
1 | -- 查询 2022 年入职的员工 |
NOT BETWEEN 可以排除范围:
1 | -- 查询工资不在 10000~15000 范围内的员工 |
3. IN
用于判断值是否在一个指定的集合中,替代多个 OR 条件。
1 | -- 查询技术部和销售部的员工 |
NOT IN 可以排除指定值:
1 | -- 查询不是技术部和销售部的员工 |
⚠️ 注意:
IN列表中如果包含NULL,NOT IN的结果可能不符合预期。例如WHERE salary NOT IN (15000, NULL)会返回空结果,因为任何值与NULL的比较结果都是UNKNOWN。这是一个非常容易踩的坑。
IN 还可以配合子查询使用:
1 | -- 查询工资高于平均值的部门有哪些员工 |
4. LIKE(模糊匹配)
用于字符串的模式匹配,支持两个通配符:
| 通配符 | 说明 | 示例 |
|---|---|---|
% |
匹配任意数量的字符(包括 0 个) | '张%' 匹配以「张」开头的任意字符串 |
_ |
匹配恰好 1 个字符 | '张_' 匹配「张」+ 任意一个字符 |
1 | -- 查询姓「张」的员工 |
NOT LIKE 可以排除匹配:
1 | -- 查询名字不以「张」开头的员工 |
💡 补充:
LIKE默认不区分大小写(取决于表的字符集排序规则COLLATION)。如果需要区分大小写,可以使用LIKE BINARY:
1 SELECT * FROM employee WHERE name LIKE BINARY 'zhang%';
⚠️ 性能提示:
LIKE '%xxx'(前缀有%)会导致全表扫描,无法利用索引。尽量使用LIKE 'xxx%'的形式。
5. IS NULL / IS NOT NULL
用于判断值是否为 NULL。
1 | -- 查询工资为空的员工 |
⚠️ 注意:不能用
= NULL来判断空值!WHERE salary = NULL永远返回空结果,因为NULL与任何值(包括NULL自身)的比较结果都是UNKNOWN。必须使用IS NULL。
6. 逻辑运算符与优先级
| 运算符 | 说明 |
|---|---|
AND |
逻辑与,两个条件都满足 |
OR |
逻辑或,任一条件满足 |
NOT |
逻辑非,取反 |
优先级(从高到低):NOT > AND > OR
1 | -- 查询技术部工资大于 15000 的员工 |
⚠️ 易错点:由于
AND优先级高于OR,混用时一定要注意加括号!
1 | -- ❌ 错误理解:想查询「技术部或销售部」中工资大于 15000 的人 |
💡 建议:即使你清楚优先级,也推荐使用括号来让查询意图更加明确、可读性更好。
二、分组查询(GROUP BY + HAVING)
GROUP BY 用于将数据按照某些列分组,通常搭配聚合函数一起使用,对每组数据进行统计计算。
1. 聚合函数
在讲 GROUP BY 之前,先回顾一下常用的聚合函数:
| 函数 | 说明 | 注意事项 |
|---|---|---|
COUNT(*) |
统计行数(包含 NULL) | - |
COUNT(列名) |
统计非 NULL 的行数 | 会忽略 NULL 值 |
SUM(列名) |
求和 | 忽略 NULL |
AVG(列名) |
求平均值 | 忽略 NULL(分母不含 NULL 行) |
MAX(列名) |
求最大值 | - |
MIN(列名) |
求最小值 | - |
1 | -- 统计总人数 |
⚠️ 易错点:
COUNT(*)和COUNT(列名)的区别——COUNT(*)统计所有行,COUNT(列名)只统计该列非 NULL 的行。AVG也会忽略 NULL 行,这在有缺失数据时可能导致平均值偏高。
2. GROUP BY 基础语法
1 | SELECT 分组列, 聚合函数 FROM 表名 |
1 | -- 按部门统计人数和平均工资 |
结果:
| department | emp_count | avg_salary |
|---|---|---|
| 技术部 | 4 | 16125.00 |
| 销售部 | 4 | 12000.00 |
| 人事部 | 2 | 12000.00 |
⚠️ 重要规则:
SELECT中出现的列,要么是GROUP BY的分组列,要么包裹在聚合函数中。否则在严格模式(ONLY_FULL_GROUP_BY)下会报错。
1 | -- ❌ 错误:name 既不是分组列,也没用聚合函数包裹 |
多列分组:
1 | -- 按部门和年龄段分组 |
3. HAVING 过滤分组
HAVING 用于对分组后的结果进行过滤,它是配合 GROUP BY 使用的。
1 | SELECT 分组列, 聚合函数 FROM 表名 |
1 | -- 查询平均工资大于 12000 的部门 |
1 | -- 查询员工人数大于等于 3 的部门 |
4. ⭐ WHERE vs HAVING 对比(重点)
这是最容易混淆的地方,我们从多个维度进行对比:
| 对比维度 | WHERE | HAVING |
|---|---|---|
| 作用对象 | 过滤原始行 | 过滤分组后的结果 |
| 执行时机 | GROUP BY 之前 |
GROUP BY 之后 |
| 能否使用聚合函数 | ❌ 不能 | ✅ 可以 |
| 能否使用列别名 | ❌ 不能(MySQL 中不行) | ✅ 可以(MySQL 支持) |
| 是否依赖 GROUP BY | 不依赖,可以单独使用 | 通常需要配合 GROUP BY |
SQL 执行顺序:
1 | FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT |
💡 理解这个执行顺序是区分
WHERE和HAVING的关键。WHERE在分组前过滤,所以它无法使用聚合函数;HAVING在分组后过滤,所以它可以用聚合函数作为条件。
对比示例:
1 | -- 需求:查询「技术部和销售部」中,平均工资大于 12000 的部门 |
1 | -- ❌ 错误:WHERE 中不能使用聚合函数 |
💡 简单记忆法:
- 能用
WHERE的条件就尽量用WHERE(先过滤可以减少分组的数据量,性能更好)- 涉及聚合函数的条件,只能用
HAVING
三、CASE WHEN 条件表达式
CASE WHEN 是 SQL 中的条件表达式,类似于编程语言中的 if-else,可以在查询中根据条件返回不同的值。它是 SQL 标准语法,所有数据库都支持。
1. 两种语法形式
形式一:简单 CASE(等值匹配)
1 | CASE 表达式 |
1 | -- 将部门名转为英文 |
形式二:搜索 CASE(条件判断,更灵活)
1 | CASE |
1 | -- 按工资划分等级 |
⚠️ 注意事项:
CASE WHEN是按顺序匹配的,匹配到第一个满足的条件就停止,后面的不再判断。所以条件的顺序很重要。ELSE是可选的,如果省略ELSE且没有条件匹配,返回NULL。- 别忘了最后的
END关键字!这是最常见的语法错误。- 搜索 CASE 比简单 CASE 更强大,建议优先使用搜索 CASE。
2. 在 SELECT 中使用(数据分类/转换)
这是 CASE WHEN 最常见的用法,用来根据条件生成新的列。
1 | -- 根据入职年份给员工打标签 |
3. 在 WHERE 中使用(条件筛选)
1 | -- 筛选出不同部门满足不同工资标准的员工 |
4. 在 ORDER BY 中使用(自定义排序)
1 | -- 自定义部门排序:技术部排第一,销售部第二,其余最后 |
5. 在 GROUP BY 中使用(按条件分组)
1 | -- 按工资区间分组统计人数 |
6. ⭐ 搭配聚合函数(条件统计)
这是 CASE WHEN 最实用也最强大的用法之一——在一次查询中按不同条件分别统计。
1 | -- 一次查询统计各部门的工资分布 |
💡 补充:
SUM(CASE WHEN ... THEN 1 ELSE 0 END)和COUNT(CASE WHEN ... THEN 1 END)效果一样,都是条件计数。区别是COUNT版本不需要ELSE 0(因为COUNT不统计 NULL)。
7. ⭐ 行转列(经典应用)
CASE WHEN + 聚合函数实现行转列,这是非常经典的用法。
假设有一张成绩表:
1 | CREATE TABLE score ( |
行转列:将每个科目从行变成列
1 | SELECT |
结果:
| student | 语文 | 数学 | 英语 |
|---|---|---|---|
| 张三 | 85 | 92 | 78 |
| 李四 | 90 | 88 | 95 |
| 王五 | 75 | 60 | 82 |
💡 这里用
MAX是因为每个学生每科只有一条记录,MAX用来”去掉”NULL(没匹配到的科目返回 NULL,MAX会忽略 NULL 取出有效值)。用SUM或MIN也可以。
8. CASE WHEN vs IF()
MySQL 还提供了 IF() 函数,在只有两个分支的情况下可以替代简单的 CASE WHEN:
1 | -- IF(条件, 为真的值, 为假的值) |
| 对比 | CASE WHEN | IF() |
|---|---|---|
| 分支数量 | 支持多分支 | 仅支持 2 个分支 |
| SQL 标准 | ✅ 标准语法 | ❌ MySQL 特有 |
| 可读性 | 多分支时更清晰 | 简单场景更简洁 |
| 推荐场景 | 通用场景 | 简单的二选一 |
💡 建议:简单的真/假判断用
IF(),多条件分支用CASE WHEN。如果考虑跨数据库兼容,统一使用CASE WHEN。
四、综合对比速查表
| 关键字 | 作用 | 执行时机 | 能否用聚合函数 |
|---|---|---|---|
WHERE |
过滤行 | GROUP BY 之前 |
❌ |
GROUP BY |
分组数据 | WHERE 之后 |
搭配聚合函数使用 |
HAVING |
过滤分组 | GROUP BY 之后 |
✅ |
CASE WHEN |
条件表达式 | 取决于所在位置 | 可以搭配使用 |
完整的 SQL 执行顺序:
1 | FROM -- 确定数据来源 |
五、LeetCode 相关练习
🔗 后续待补充..