零基础学SQL 10:WHERE里写别名就报错?SQL真实执行顺序一次搞懂

先说点真话

写 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订单数
12

员工 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 行。

姓名部门工资入职日期
张三技术部90002019-03-01
李四技术部90002020-07-15
王五销售部92002018-01-10
赵六销售部92002021-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(工资),才给它们起名"人数"“平均工资”——别名在这里出生

部门人数平均工资
技术部29000
销售部19200

第 6 步 ORDER BY 平均工资 DESC:别名已出生,能用。销售部(9200)排到前面。

第 7 步 LIMIT 1:只留第一行。

部门人数平均工资
销售部19200

一条 SQL,七步走完。你以后看任何复杂 SQL,都可以用这七步在脑子里"人肉执行"一遍,哪里会报错、哪里拿不到数据,一目了然。


五、执行顺序避坑清单(收藏这张表)

现象根源正确做法
WHERE 里用别名报 1054WHERE 先于 SELECT,别名没出生表达式直接写 WHERE,或子查询包裹
WHERE 里写 COUNT 报 1111WHERE 在分组前执行,没有"组"可算筛组条件改放 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,          '待分配');
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从零数据分析

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值