先说点真话
写 SQL 的人,几乎都在同一个地方摔过跤:
SELECT 姓名, 工资 * 12 AS 年薪
FROM 员工表
WHERE 年薪 > 109000;
看逻辑毫无问题:先算出年薪,再筛出年薪超过 10.9 万的人。一运行——
ERROR 1054 (42S22): Unknown column '年薪' in 'where clause'
"年薪"这列明明就在 SELECT 里写着,AS 年薪 白纸黑字,数据库却说不认识这一列。刚学 SQL 的人到这里直接怀疑人生:是不是我表建错了?
表没错,你也没错,错的是你对执行顺序的想象:你以为 SQL 从上往下、从前往后执行,实际上 SELECT 是倒数第三个执行的。别名是在 SELECT 阶段才"出生"的,而 WHERE 排在它前面——WHERE 执行的时候,"年薪"这个名字根本还不存在。
这篇把执行顺序彻底讲透。它是基础篇的收官,因为后面进阶篇的 JOIN、子查询、窗口函数,全都建立在这个地基上。
💡 「零基础学SQL」系列持续更新中,关注我不迷路,每篇都带标准练习数据,复制就能跑。
一、书写顺序 ≠ 执行顺序
你写的 SQL 是这个顺序:
SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
数据库实际执行是这个顺序:
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
口诀上一篇给过:F-W-G-H-S-O-L。为什么这么设计?说两个核心原因:
1. WHERE 必须先干活。 一张表百万行,先 WHERE 过滤掉 99 万行,再去做分组、聚合,计算量差着几个量级。要是先 SELECT 再 WHERE,等于把废数据也全算了一遍。
2. SELECT 是"投影",最后才决定给你看什么。 前面所有步骤都在跟"整行数据"打交道,只有到了 SELECT 这一步,才决定输出哪几列、起什么别名。
记住一个时间点:别名在 SELECT 阶段才出生。 所以排 SELECT 前面的(WHERE、GROUP BY、HAVING)按标准都不认识别名;排在后面的(ORDER BY、LIMIT)认识。下面用实验逐个验证。
二、三个别名实验,亲手验证执行顺序
实验 1:WHERE 用别名 → 报错 1054
-- 意图:筛出年薪超过 10.9 万的员工
SELECT 姓名, 工资 * 12 AS 年薪
FROM 员工表
WHERE 年薪 > 109000;
ERROR 1054 (42S22): Unknown column '年薪' in 'where clause'
原因:WHERE 在 SELECT 之前执行,此刻"年薪"还没被命名,数据库去找员工表里叫"年薪"的列,找不到,报 1054。报错信息里的 where clause 直接告诉你死在哪一步。
改法一(简单场景):把表达式原样写进 WHERE。
SELECT 姓名, 工资 * 12 AS 年薪
FROM 员工表
WHERE 工资 * 12 > 109000;
| 姓名 | 年薪 |
|---|---|
| 王五 | 110400.00 |
| 赵六 | 110400.00 |
改法二(通用):子查询包裹,先算好再筛。表达式复杂、或者筛选条件要复用别名时用这招。
SELECT *
FROM (
SELECT 姓名, 工资 * 12 AS 年薪
FROM 员工表
) AS t
WHERE t.年薪 > 109000;
内层 SELECT 先执行完,"年薪"这列在子查询结果里真实存在了,外层 WHERE 自然能用。这个"子查询包裹"的写法,是后面进阶篇的高频操作,现在必须练熟。
实验 2:ORDER BY 用别名 → 正常
SELECT 姓名, 工资 * 12 AS 年薪
FROM 员工表
ORDER BY 年薪 DESC;
| 姓名 | 年薪 |
|---|---|
| 王五 | 110400.00 |
| 赵六 | 110400.00 |
| 张三 | 108000.00 |
| 李四 | 108000.00 |
不报错。因为 ORDER BY 在 SELECT 之后执行,别名已经出生,认识。
同一个别名,WHERE 报错、ORDER BY 正常——这不是玄学,就是执行顺序。
实验 3:GROUP BY / HAVING 用别名 → MySQL 能跑,但别依赖
SELECT 部门, AVG(工资) AS 平均工资
FROM 员工表
GROUP BY 部门
HAVING 平均工资 > 9100;
| 部门 | 平均工资 |
|---|---|
| 销售部 | 9200.000000 |
在 MySQL 里能跑出结果(销售部 9200)。但注意:这是 MySQL 的方言扩展,不是 SQL 标准。同样的语句放到 SQL Server 里,报 Invalid column name '平均工资'(错误 207)。按执行顺序,GROUP BY、HAVING 也排在 SELECT 前面,理论上同样不该认识别名。
跨库兼容的标准写法,HAVING 里直接写聚合表达式:
SELECT 部门, AVG(工资) AS 平均工资
FROM 员工表
GROUP BY 部门
HAVING AVG(工资) > 9100;
一句话总结三组实验:WHERE 里的别名,打死都别用(报 1054);GROUP BY / HAVING 里的别名,MySQL 给面子但别依赖;ORDER BY 里的别名,放心用。
三、WHERE 里写 COUNT 也报错:同一个根源
第二个高频翻车点,比别名更隐蔽:
-- 意图:找下单超过 1 笔的员工
SELECT 员工id, COUNT(*) AS 订单数
FROM 订单表
WHERE COUNT(*) > 1
GROUP BY 员工id;
ERROR 1111 (HY000): Invalid use of group function
原因还是执行顺序:COUNT 这类聚合函数,是对"分组之后的一组行"做统计的。而 WHERE 是分组之前、一行一行做筛选的——一行数据怎么算 COUNT?数据库直接报 1111,告诉你"聚合函数用错了地方"。
对组做筛选,必须用 HAVING:
SELECT 员工id, COUNT(*) AS 订单数
FROM 订单表
GROUP BY 员工id
HAVING COUNT(*) > 1;
| 员工id | 订单数 |
|---|---|
| 1 | 2 |
员工 1(张三)有两笔订单(101、102),员工 2 和 3 各一笔,所以只有 1 留下。
WHERE 筛行,HAVING 筛组——这就是它俩的分工,根源同样是执行顺序。
四、一条完整 SQL,逐层拆解执行过程
现在把一条稍复杂的 SQL 拆开,看数据在每一步是什么样子:
SELECT 部门, COUNT(*) AS 人数, AVG(工资) AS 平均工资
FROM 员工表
WHERE 入职日期 < '2021-01-01'
GROUP BY 部门
HAVING COUNT(*) >= 1
ORDER BY 平均工资 DESC
LIMIT 1;
第 1 步 FROM 员工表:拿到全部 4 行。
| 姓名 | 部门 | 工资 | 入职日期 |
|---|---|---|---|
| 张三 | 技术部 | 9000 | 2019-03-01 |
| 李四 | 技术部 | 9000 | 2020-07-15 |
| 王五 | 销售部 | 9200 | 2018-01-10 |
| 赵六 | 销售部 | 9200 | 2021-05-20 |
第 2 步 WHERE 入职日期 < ‘2021-01-01’:赵六(2021-05-20)被筛掉,剩 3 行。注意这步只筛行,别名列名都不存在。
第 3 步 GROUP BY 部门:3 行按部门归堆——技术部 2 人(张三、李四),销售部 1 人(王五)。
第 4 步 HAVING COUNT(*) >= 1:两组都满足,都保留。
第 5 步 SELECT:这时才计算每组的 COUNT(*) 和 AVG(工资),才给它们起名"人数"“平均工资”——别名在这里出生。
| 部门 | 人数 | 平均工资 |
|---|---|---|
| 技术部 | 2 | 9000 |
| 销售部 | 1 | 9200 |
第 6 步 ORDER BY 平均工资 DESC:别名已出生,能用。销售部(9200)排到前面。
第 7 步 LIMIT 1:只留第一行。
| 部门 | 人数 | 平均工资 |
|---|---|---|
| 销售部 | 1 | 9200 |
一条 SQL,七步走完。你以后看任何复杂 SQL,都可以用这七步在脑子里"人肉执行"一遍,哪里会报错、哪里拿不到数据,一目了然。
五、执行顺序避坑清单(收藏这张表)
| 现象 | 根源 | 正确做法 |
|---|---|---|
| WHERE 里用别名报 1054 | WHERE 先于 SELECT,别名没出生 | 表达式直接写 WHERE,或子查询包裹 |
| WHERE 里写 COUNT 报 1111 | WHERE 在分组前执行,没有"组"可算 | 筛组条件改放 HAVING |
| GROUP BY / HAVING 用别名跨库报 207 | 方言扩展,非 SQL 标准 | 直接写列名或聚合表达式 |
| 想先排序再取前 N 条 | ORDER BY 先于 LIMIT 执行,天然满足 | ORDER BY + LIMIT 直接写 |
| 想对子查询结果再筛选 | SELECT 结果没法直接接 WHERE | 子查询包裹,外层 WHERE |
两个高频错误码,记下来能救命:1054(Unknown column,列不存在)和 1111(Invalid use of group function,聚合函数用错位置)。报错信息里都会写明死在哪一步(比如 where clause),顺着报错定位执行顺序问题,比自己瞎猜快十倍。
六、练习题(先自己写,再看答案)
题目 1: 下面这条 SQL 会报错,说出错误码,并给出两种改法:
SELECT 姓名, 工资 * 12 AS 年薪
FROM 员工表
WHERE 年薪 > 100000;
题目 2: 查每个部门工资超过 9000 的员工人数(注意:先筛人再分组,还是先分组再筛组?),只保留人数不少于 1 人的部门。
题目 3: 下面这条 SQL 想找"付款总金额超过 5000 的员工",运行报错。指出错在哪一步,并改正:
SELECT 员工id, SUM(订单金额) AS 总金额
FROM 订单表
WHERE 状态 = '已付款' AND SUM(订单金额) > 5000
GROUP BY 员工id;
题目 4: 按平均工资从高到低排列部门,只显示平均工资、部门两列,且只取第一名。
参考答案
题目 1: 报 ERROR 1054 (42S22): Unknown column '年薪' in 'where clause'。改法一,表达式直接写:WHERE 工资 * 12 > 100000;改法二,子查询包裹:
SELECT *
FROM (SELECT 姓名, 工资 * 12 AS 年薪 FROM 员工表) AS t
WHERE t.年薪 > 100000;
-- 4 人年薪都超过 10 万,全部返回
题目 2: "工资超过 9000"是筛行(单个员工),用 WHERE,在分组前执行:
SELECT 部门, COUNT(*) AS 人数
FROM 员工表
WHERE 工资 > 9000
GROUP BY 部门
HAVING COUNT(*) >= 1;
结果:销售部 2 人(王五、赵六都是 9200)。技术部两人都是 9000,不大于 9000,被 WHERE 筛掉,整组消失。
题目 3: 错在 WHERE 里写了 SUM。报 ERROR 1111 (HY000): Invalid use of group function——WHERE 在 GROUP BY 之前执行,此刻没有组,SUM 无从算起。聚合条件改放 HAVING:
SELECT 员工id, SUM(订单金额) AS 总金额
FROM 订单表
WHERE 状态 = '已付款'
GROUP BY 员工id
HAVING SUM(订单金额) > 5000;
结果:员工 1(3000 + 2500 = 5500)。员工 3 只有 4000,被 HAVING 筛掉;员工 2 的订单是待付款,WHERE 阶段就出局了。
题目 4:
SELECT 部门, AVG(工资) AS 平均工资
FROM 员工表
GROUP BY 部门
ORDER BY 平均工资 DESC
LIMIT 1;
结果:销售部,9200。ORDER BY 在 SELECT 后执行,用别名"平均工资"合法。
你被哪条报错坑过?
我带新人的时候,“WHERE 里写 COUNT” 这个坑,几乎每个人都要踩一次,包括当年的我自己。踩完再看执行顺序,才真的记住。
上面实验里的报错,你撞过哪一条?是 1054 还是 1111?评论区报个数,我看看哪个坑最深。
从零数据分析 · 十年数据分析经验 · 实战笔记
SQL 从入门到精通 28 篇持续更新中,每篇附标准练习数据,复制即运行。
关注我,把 SQL 学成肌肉记忆。
📋 系列目录:零基础学SQL:从入门到精通完整目录(28篇持续更新)
📖 上篇:篇09 CONCAT一拼就出NULL?常用函数详解+实战避坑
⏭️ 下篇预告:进阶篇篇13 别名与多表连接进阶——JOIN 一写就晕?别名才是解药(基础篇 01-10 到此完结,进阶篇见)
附录:标准练习数据(复制即可运行,全系列通用)
-- 全系列通用四张表,每次运行先 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, '待分配');

282

被折叠的 条评论
为什么被折叠?



