ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

零基础学SQL 10:WHERE里写别名就报错?SQL真实执行顺序一次搞懂 先说点真话写 SQL 的人几乎都在同一个地方摔过跤SELECT姓名,工资*12AS年薪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认识。下面用实验逐个验证。二、三个别名实验亲手验证执行顺序实验 1WHERE 用别名 → 报错 1054-- 意图筛出年薪超过 10.9 万的员工SELECT姓名,工资*12AS年薪FROM员工表WHERE年薪109000;ERROR 1054 (42S22): Unknown column 年薪 in where clause原因WHERE 在 SELECT 之前执行此刻年薪还没被命名数据库去找员工表里叫年薪的列找不到报 1054。报错信息里的where clause直接告诉你死在哪一步。改法一简单场景把表达式原样写进 WHERE。SELECT姓名,工资*12AS年薪FROM员工表WHERE工资*12109000;姓名年薪王五110400.00赵六110400.00改法二通用子查询包裹先算好再筛。表达式复杂、或者筛选条件要复用别名时用这招。SELECT*FROM(SELECT姓名,工资*12AS年薪FROM员工表)AStWHEREt.年薪109000;内层 SELECT 先执行完年薪这列在子查询结果里真实存在了外层 WHERE 自然能用。这个子查询包裹的写法是后面进阶篇的高频操作现在必须练熟。实验 2ORDER BY 用别名 → 正常SELECT姓名,工资*12AS年薪FROM员工表ORDERBY年薪DESC;姓名年薪王五110400.00赵六110400.00张三108000.00李四108000.00不报错。因为 ORDER BY 在 SELECT 之后执行别名已经出生认识。同一个别名WHERE 报错、ORDER BY 正常——这不是玄学就是执行顺序。实验 3GROUP BY / HAVING 用别名 → MySQL 能跑但别依赖SELECT部门,AVG(工资)AS平均工资FROM员工表GROUPBY部门HAVING平均工资9100;部门平均工资销售部9200.000000在 MySQL 里能跑出结果销售部 9200。但注意这是 MySQL 的方言扩展不是 SQL 标准。同样的语句放到 SQL Server 里报Invalid column name 平均工资错误 207。按执行顺序GROUP BY、HAVING 也排在 SELECT 前面理论上同样不该认识别名。跨库兼容的标准写法HAVING 里直接写聚合表达式SELECT部门,AVG(工资)AS平均工资FROM员工表GROUPBY部门HAVINGAVG(工资)9100;一句话总结三组实验WHERE 里的别名打死都别用报 1054GROUP BY / HAVING 里的别名MySQL 给面子但别依赖ORDER BY 里的别名放心用。三、WHERE 里写 COUNT 也报错同一个根源第二个高频翻车点比别名更隐蔽-- 意图找下单超过 1 笔的员工SELECT员工id,COUNT(*)AS订单数FROM订单表WHERECOUNT(*)1GROUPBY员工id;ERROR 1111 (HY000): Invalid use of group function原因还是执行顺序COUNT 这类聚合函数是对分组之后的一组行做统计的。而 WHERE 是分组之前、一行一行做筛选的——一行数据怎么算 COUNT数据库直接报 1111告诉你聚合函数用错了地方。对组做筛选必须用 HAVINGSELECT员工id,COUNT(*)AS订单数FROM订单表GROUPBY员工idHAVINGCOUNT(*)1;员工id订单数12员工 1张三有两笔订单101、102员工 2 和 3 各一笔所以只有 1 留下。WHERE 筛行HAVING 筛组——这就是它俩的分工根源同样是执行顺序。四、一条完整 SQL逐层拆解执行过程现在把一条稍复杂的 SQL 拆开看数据在每一步是什么样子SELECT部门,COUNT(*)AS人数,AVG(工资)AS平均工资FROM员工表WHERE入职日期2021-01-01GROUPBY部门HAVINGCOUNT(*)1ORDERBY平均工资DESCLIMIT1;第 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 在分组前执行没有组可算筛组条件改放 HAVINGGROUP BY / HAVING 用别名跨库报 207方言扩展非 SQL 标准直接写列名或聚合表达式想先排序再取前 N 条ORDER BY 先于 LIMIT 执行天然满足ORDER BY LIMIT 直接写想对子查询结果再筛选SELECT 结果没法直接接 WHERE子查询包裹外层 WHERE两个高频错误码记下来能救命1054Unknown column列不存在和 1111Invalid use of group function聚合函数用错位置。报错信息里都会写明死在哪一步比如where clause顺着报错定位执行顺序问题比自己瞎猜快十倍。六、练习题先自己写再看答案题目 1下面这条 SQL 会报错说出错误码并给出两种改法SELECT姓名,工资*12AS年薪FROM员工表WHERE年薪100000;题目 2查每个部门工资超过 9000 的员工人数注意先筛人再分组还是先分组再筛组只保留人数不少于 1 人的部门。题目 3下面这条 SQL 想找付款总金额超过 5000 的员工运行报错。指出错在哪一步并改正SELECT员工id,SUM(订单金额)AS总金额FROM订单表WHERE状态已付款ANDSUM(订单金额)5000GROUPBY员工id;题目 4按平均工资从高到低排列部门只显示平均工资、部门两列且只取第一名。参考答案题目 1报ERROR 1054 (42S22): Unknown column 年薪 in where clause。改法一表达式直接写WHERE 工资 * 12 100000改法二子查询包裹SELECT*FROM(SELECT姓名,工资*12AS年薪FROM员工表)AStWHEREt.年薪100000;-- 4 人年薪都超过 10 万全部返回题目 2工资超过 9000是筛行单个员工用 WHERE在分组前执行SELECT部门,COUNT(*)AS人数FROM员工表WHERE工资9000GROUPBY部门HAVINGCOUNT(*)1;结果销售部 2 人王五、赵六都是 9200。技术部两人都是 9000不大于 9000被 WHERE 筛掉整组消失。题目 3错在 WHERE 里写了 SUM。报ERROR 1111 (HY000): Invalid use of group function——WHERE 在 GROUP BY 之前执行此刻没有组SUM 无从算起。聚合条件改放 HAVINGSELECT员工id,SUM(订单金额)AS总金额FROM订单表WHERE状态已付款GROUPBY员工idHAVINGSUM(订单金额)5000;结果员工 13000 2500 5500。员工 3 只有 4000被 HAVING 筛掉员工 2 的订单是待付款WHERE 阶段就出局了。题目 4SELECT部门,AVG(工资)AS平均工资FROM员工表GROUPBY部门ORDERBY平均工资DESCLIMIT1;结果销售部9200。ORDER BY 在 SELECT 后执行用别名平均工资合法。你被哪条报错坑过我带新人的时候“WHERE 里写 COUNT” 这个坑几乎每个人都要踩一次包括当年的我自己。踩完再看执行顺序才真的记住。上面实验里的报错你撞过哪一条是 1054 还是 1111评论区报个数我看看哪个坑最深。从零数据分析· 十年数据分析经验 · 实战笔记SQL 从入门到精通 28 篇持续更新中每篇附标准练习数据复制即运行。关注我把 SQL 学成肌肉记忆。 系列目录零基础学SQL从入门到精通完整目录28篇持续更新 上篇篇09 CONCAT一拼就出NULL常用函数详解实战避坑⏭️ 下篇预告进阶篇篇13 别名与多表连接进阶——JOIN 一写就晕别名才是解药基础篇 01-10 到此完结进阶篇见附录标准练习数据复制即可运行全系列通用-- 全系列通用四张表每次运行先 DROP 再 CREATE不会报表已存在DROPTABLEIFEXISTS员工表;CREATETABLE员工表(员工idINTPRIMARYKEY,姓名VARCHAR(20),部门VARCHAR(20),工资DECIMAL(10,2),邮箱VARCHAR(50),手机VARCHAR(20),入职日期DATE);INSERTINTO员工表(员工id,姓名,部门,工资,邮箱,手机,入职日期)VALUES(1,张三,技术部,9000.00,zhangsandemo.com,13800000001,2019-03-01),(2,李四,技术部,9000.00,lisidemo.com,13800000002,2020-07-15),(3,王五,销售部,9200.00,wangwudemo.com,13800000003,2018-01-10),(4,赵六,销售部,9200.00,zhaoliudemo.com,13800000004,2021-05-20);DROPTABLEIFEXISTS订单表;CREATETABLE订单表(订单idINTPRIMARYKEY,员工idINT,订单金额DECIMAL(10,2),下单时间DATETIME,付款时间DATETIME,状态VARCHAR(20));INSERTINTO订单表(订单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,待付款);DROPTABLEIFEXISTS用户表;CREATETABLE用户表(用户idINTPRIMARYKEY,姓名VARCHAR(20),手机VARCHAR(20),邮箱VARCHAR(50),地址VARCHAR(100));INSERTINTO用户表(用户id,姓名,手机,邮箱,地址)VALUES(1,张三,13900000001,zhangsandemo.com,北京市朝阳区),(2,李四,13900000002,lisidemo.com,上海市浦东新区),(3,王五,13900000003,wangwudemo.com,广州市天河区);DROPTABLEIFEXISTS任务表;CREATETABLE任务表(任务idINTPRIMARYKEY,员工idINT,备注VARCHAR(100),状态VARCHAR(20));INSERTINTO任务表(任务id,员工id,备注,状态)VALUES(1,1,完成需求评审,已完成),(2,2,NULL,进行中),(3,3,修复线上bug,已完成),(4,4,NULL,待分配);
返回列表