先说点真话
当年做数据分析项目,有回老板要"按部门算平均工资,只保留平均超过 9100 的部门"。新人交上来的 SQL 八成报错——要么把筛平均工资的判断写成了 WHERE AVG(工资) > 9100(直接语法报错),要么 WHERE 和 GROUP BY 顺序乱写。
其实就差一件事没搞懂:排序、分组、分组后过滤,这三块各自在哪个阶段生效。今天把它们一次讲透,你以后再写这类报表就不慌了。
💡 「SQL 从入门到精通」系列持续更新中,关注我不迷路,每篇都是实战干货。
一、ORDER BY:查完之后,给结果排个序
你会什么场景下用到它? 你 SELECT 把数据查出来了,但想按"工资从高到低"看谁排前面、谁垫底,或者按"入职日期从早到晚"看资历。这时候就要在查询最后加 ORDER BY。
正确写法:
SELECT 姓名, 工资
FROM 员工表
ORDER BY 工资 DESC; — DESC 从高到低;ASC 从低到高(默认)
结果(按工资降序):
| 王五 | 9200 |
| 赵六 | 9200 |
| 张三 | 9000 |
| 李四 | 9000 |
几个要点:
- 可以多列排序:ORDER BY 部门 ASC, 工资 DESC(先按部门,同部门内再按工资)。
- ORDER BY 永远写在查询最后(在 WHERE、GROUP BY、HAVING 之后)。
我当年踩的坑: 想按"算出来的平均工资"排序,写成 ORDER BY 平均 但 SELECT 里给列起了别名,有的数据库认别名、有的不认,最稳的是直接写 ORDER BY AVG(工资)。
二、GROUP BY:按维度归类汇总(必须搭配聚合函数)
你会什么场景下用到它? 你想"按部门统计人数、算平均工资",而不是一行行看。这种"先分组、再对每组算一个数"的需求,GROUP BY 就是干这个的。
但这里有个前提: GROUP BY 自己不会算数,它必须配合聚合函数——COUNT()(计数)、SUM()(求和)、AVG()(平均)、MAX()(最大)、MIN()(最小)。
正确写法:
SELECT 部门, AVG(工资) AS 平均工资, COUNT(*) AS 人数
FROM 员工表
GROUP BY 部门;
结果:
| 技术部 | 9000.00 | 2 |
| 销售部 | 9200.00 | 2 |
最大的坑(新手必踩): SELECT 里出现的非聚合列,必须同时出现在 GROUP BY 后面,否则直接报错。比如下面这种就会报错:
— ❌ 错误:姓名没进 GROUP BY,数据库不知道"每个部门的姓名该显示哪一行"
SELECT 部门, 姓名, AVG(工资) FROM 员工表 GROUP BY 部门;
记住口诀:分组查什么维度,SELECT 和 GROUP BY 就列什么维度,其余一律包进聚合函数。
三、HAVING:分组"之后"再过滤(和 WHERE 不是一回事)
你会什么场景下用到它? 接上面的例子:你按部门算完平均工资了,但只想看"平均超过 9100 的部门"。这个"平均>9100"是分组之后才产生的数,不能用 WHERE 去筛——因为 WHERE 是在分组之前过滤原始行的,它根本不知道"平均工资"是什么。
正确写法:
SELECT 部门, AVG(工资) AS 平均工资
FROM 员工表
GROUP BY 部门
HAVING AVG(工资) > 9100; — 分组之后,再筛分组结果
结果(只有销售部 9200 达标):
| 销售部 | 9200.00 |
四、WHERE vs HAVING,一图分清(重点)
这是新手最容易混的一对,记住这个类比就通了:
WHERE 像"进考场前查身份证"——在分组/汇总之前过滤原始行,不能用聚合函数。 HAVING 像"考完试按分数筛人"——在分组/汇总之后过滤分组结果,能用聚合函数。
| 生效阶段 | 分组前 | 分组后 |
| 能不能用 AVG()/SUM() 等 | ❌ 不能 | ✅ 能 |
| 典型用途 | 过滤某行(如工资>8000) | 过滤某组(如平均>9100) |
错误示范(别这么写):
— ❌ 报错:WHERE 里不能用聚合函数
SELECT 部门, AVG(工资) FROM 员工表 WHERE AVG(工资) > 9100 GROUP BY 部门;
— ✅ 正确:筛分组结果用 HAVING
SELECT 部门, AVG(工资) FROM 员工表 GROUP BY 部门 HAVING AVG(工资) > 9100;
两者还能一起用: 比如"只看技术部,且技术部平均>9100"——WHERE 部门='技术部'(分组前先砍掉其他部门)+ HAVING AVG(工资)>9100(分组后再筛)。
五、当年我踩过的 3 个坑(重点看)
把 HAVING 的条件写成 WHERE 想筛"平均>9100 的部门",手滑写成 WHERE AVG(工资)>9100,直接报语法错。结论:对"分组后算出来的值"做判断,一律用 HAVING。
SELECT 里的非聚合列没进 GROUP BY 写了 SELECT 部门, 姓名, COUNT(*),却只 GROUP BY 部门,数据库不知道姓名取哪行,报错。结论:分组维度要前后一致。
ORDER BY 放错位置 写在 GROUP BY 前面,报"列无效"。结论:逻辑执行顺序永远是 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。
注意:这是"引擎心里跑的顺序",不是你"写代码的顺序"。你写 SQL 时把 SELECT 放第一句只是语法规定(方便人读),引擎其实最后才执行它——SELECT 这一步干的是"投影":等前面的行和组都定了,才决定每一行输出哪些列、算哪些表达式、起什么别名。也正因为 SELECT 在 WHERE 之后,你不能在 WHERE 里用 SELECT 里起的别名(比如 WHERE 年薪>10万 而 年薪 是 SELECT 里算的,会报错),这正好证明 WHERE 比 SELECT 先跑。
六、3 个练习(自己跑一遍)
基于下面的 员工表:
参考答案(先自己写,再对照):
— 1
SELECT 姓名, 工资 FROM 员工表 ORDER BY 工资 ASC;
— 2
SELECT 部门, MAX(工资) AS 最高工资 FROM 员工表 GROUP BY 部门;
— 3(只用到订单表即可:员工id 与 订单金额 都在订单表里,无需 JOIN;若想显示姓名才需再 JOIN 员工表)
SELECT o.员工id, SUM(o.订单金额) AS 合计
FROM 订单表 o
GROUP BY o.员工id
HAVING SUM(o.订单金额) > 5000;
七、小结
- ORDER BY:查完排序,写在最后,多列用逗号。
- GROUP BY:按维度汇总,必须配合聚合函数,SELECT 非聚合列要进 GROUP BY。
- WHERE 管"分组前",HAVING 管"分组后",对平均值/总和做判断用 HAVING。
- 执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。
下一篇我们讲 条件过滤进阶:AND / OR / IN / BETWEEN / LIKE,把 WHERE 里的组合拳打全。
互动时间(站队投票):你写 GROUP BY 时最容易忘的是哪块?我当年铁定选"把 HAVING 的条件写成 WHERE,然后对着报错发呆半小时"😂。是 HAVING 的位置、还是 SELECT 非聚合列要进 GROUP BY、还是 ORDER BY 顺序?评论区打 1/2/3,我挑典型的下篇专门讲。
👋 我是 Hello_world12131,十年数据分析经验。 这篇有用的话 点个赞 + 收藏,顺便 关注我 📌 📎 系列目录(置顶) | 🔗 相关推荐: 篇02 SELECT/FROM/WHERE · SQL 常用函数速查表 · SQL 多表关联查询入门 | ⏭️ 下篇预告:篇04 条件过滤 AND/OR/IN/BETWEEN/LIKE
📋 SQL 系列统一练习表(复制即可运行,可重复执行,不会报"表已存在")
本系列所有文章都基于下面这 4 张固定表。每张表先 DROP 再 CREATE,所以每读一篇文重新跑一次这个块都安全,不用从新建表。
DROP TABLE IF EXISTS 员工表;
CREATE TABLE 员工表 (
员工id INT PRIMARY KEY,
姓名 VARCHAR(20),
部门 VARCHAR(20),
工资 DECIMAL(10,2),
邮箱 VARCHAR(50),
手机 VARCHAR(20),
入职日期 DATE
);
INSERT INTO 员工表 (员工id, 姓名, 部门, 工资, 邮箱, 手机, 入职日期) VALUES
(1, '张三', '技术部', 9000.00, 'zhangsan@demo.com', '13800000001', '2019-03-01'),
(2, '李四', '技术部', 9000.00, 'lisi@demo.com', '13800000002', '2020-07-15'),
(3, '王五', '销售部', 9200.00, 'wangwu@demo.com', '13800000003', '2018-01-10'),
(4, '赵六', '销售部', 9200.00, 'zhaoliu@demo.com', '13800000004', '2021-05-20');
DROP TABLE IF EXISTS 订单表;
CREATE TABLE 订单表 (
订单id INT PRIMARY KEY,
员工id INT,
订单金额 DECIMAL(10,2),
下单时间 DATETIME,
付款时间 DATETIME,
状态 VARCHAR(20)
);
INSERT INTO 订单表 (订单id, 员工id, 订单金额, 下单时间, 付款时间, 状态) VALUES
(101, 1, 3000.00, '2026-01-10 10:00:00', '2026-01-10 10:05:00', '已付款'),
(102, 1, 2500.00, '2026-02-15 14:00:00', '2026-02-15 14:10:00', '已付款'),
(103, 3, 4000.00, '2026-03-20 09:30:00', '2026-03-20 09:40:00', '已付款'),
(104, 2, 1500.00, '2026-04-05 16:00:00', NULL, '待付款');
DROP TABLE IF EXISTS 用户表;
CREATE TABLE 用户表 (
用户id INT PRIMARY KEY,
姓名 VARCHAR(20),
手机 VARCHAR(20),
邮箱 VARCHAR(50),
地址 VARCHAR(100)
);
INSERT INTO 用户表 (用户id, 姓名, 手机, 邮箱, 地址) VALUES
(1, '张三', '13900000001', 'zhangsan@demo.com', '北京市朝阳区'),
(2, '李四', '13900000002', 'lisi@demo.com', '上海市浦东新区'),
(3, '王五', '13900000003', 'wangwu@demo.com', '广州市天河区');
DROP TABLE IF EXISTS 任务表;
CREATE TABLE 任务表 (
任务id INT PRIMARY KEY,
员工id INT,
备注 VARCHAR(100),
状态 VARCHAR(20)
);
INSERT INTO 任务表 (任务id, 员工id, 备注, 状态) VALUES
(1, 1, '完成需求评审', '已完成'),
(2, 2, NULL, '进行中'),
(3, 3, '修复线上bug', '已完成'),
(4, 4, NULL, '待分配');




