零基础学SQL 03:ORDER BY / GROUP BY / HAVING——查询收尾三件套,别再写错顺序

发布时间:2026/8/11 7:45:49
零基础学SQL 03:ORDER BY / GROUP BY / HAVING——查询收尾三件套,别再写错顺序 先说点真话当年做数据分析项目有回老板要按部门算平均工资只保留平均超过 9100 的部门。新人交上来的 SQL 八成报错——要么把筛平均工资的判断写成了WHERE AVG(工资) 9100直接语法报错要么WHERE和GROUP BY顺序乱写。其实就差一件事没搞懂排序、分组、分组后过滤这三块各自在哪个阶段生效。今天把它们一次讲透你以后再写这类报表就不慌了。 「SQL 从入门到精通」系列持续更新中关注我不迷路每篇都是实战干货。一、ORDER BY查完之后给结果排个序你会什么场景下用到它你SELECT把数据查出来了但想按工资从高到低看谁排前面、谁垫底或者按入职日期从早到晚看资历。这时候就要在查询最后加ORDER BY。正确写法SELECT姓名,工资FROM员工表ORDERBY工资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员工表GROUPBY部门;结果部门平均工资人数技术部9000.002销售部9200.002最大的坑新手必踩SELECT里出现的非聚合列必须同时出现在GROUP BY后面否则直接报错。比如下面这种就会报错-- ❌ 错误姓名没进 GROUP BY数据库不知道每个部门的姓名该显示哪一行SELECT部门,姓名,AVG(工资)FROM员工表GROUPBY部门;记住口诀分组查什么维度SELECT和GROUP BY就列什么维度其余一律包进聚合函数。三、HAVING分组之后再过滤和 WHERE 不是一回事你会什么场景下用到它接上面的例子你按部门算完平均工资了但只想看平均超过 9100 的部门。这个平均9100是分组之后才产生的数不能用WHERE去筛——因为WHERE是在分组之前过滤原始行的它根本不知道平均工资是什么。正确写法SELECT部门,AVG(工资)AS平均工资FROM员工表GROUPBY部门HAVINGAVG(工资)9100;-- 分组之后再筛分组结果结果只有销售部 9200 达标部门平均工资销售部9200.00四、WHERE vs HAVING一图分清重点这是新手最容易混的一对记住这个类比就通了WHERE像进考场前查身份证——在分组/汇总之前过滤原始行不能用聚合函数。HAVING像考完试按分数筛人——在分组/汇总之后过滤分组结果能用聚合函数。对比项WHEREHAVING生效阶段分组前分组后能不能用 AVG()/SUM() 等❌ 不能✅ 能典型用途过滤某行如工资8000过滤某组如平均9100错误示范别这么写-- ❌ 报错WHERE 里不能用聚合函数SELECT部门,AVG(工资)FROM员工表WHEREAVG(工资)9100GROUPBY部门;-- ✅ 正确筛分组结果用 HAVINGSELECT部门,AVG(工资)FROM员工表GROUPBY部门HAVINGAVG(工资)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 个练习自己跑一遍基于下面的员工表按工资从低到高排查出所有人的姓名和工资。按部门统计每个部门的最高工资。用订单表按员工id分组查出订单金额合计超过 5000的员工及其合计金额。SQLGROUP BY 员工idHAVING SUM(订单金额) 5000。思考题如果还想在结果里显示员工姓名要不要 JOIN员工表为什么只查订单表也够用参考答案先自己写再对照-- 1SELECT姓名,工资FROM员工表ORDERBY工资ASC;-- 2SELECT部门,MAX(工资)AS最高工资FROM员工表GROUPBY部门;-- 3只用到订单表即可员工id 与 订单金额 都在订单表里无需 JOIN若想显示姓名才需再 JOIN 员工表SELECTo.员工id,SUM(o.订单金额)AS合计FROM订单表 oGROUPBYo.员工idHAVINGSUM(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所以每读一篇文重新跑一次这个块都安全不用从新建表。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,待分配);