先说点真话
前七篇你学了 SELECT、WHERE、ORDER BY、GROUP BY、聚合函数、NULL 的坑——单拆开每个都懂,拼到一起就懵。
我带新人的时候见过太多了:给了个需求,对方盯着屏幕半天,不知道从哪开始写。问题不是学得不够,是没练过"串起来"。
今天用一个真实需求,从最简单的 SELECT 一路写到聚合+分组+排序+限制,把篇01-07的知识点一次串完。
💡 「零基础学SQL」系列持续更新中,关注我不迷路,每篇都带标准练习数据,复制就能跑。
一、需求来了
老板说:“看看每个员工的订单情况,已付款的,按总金额排序,取第一名。”
拆成 4 步:挑列 → 筛选 → 分组 → 排序+限制(HAVING 作为扩展单独讲)。每步对应你学过的一个知识点。
二、第一步:挑列(SELECT)
最朴素的写法是SELECT *,但生产环境别这么干——多查不用的列浪费网络传输、占内存,还可能暴露敏感字段(手机号、邮箱)。
-- 意图:只取需要的列SELECT订单id,员工id,订单金额,状态FROM订单表;| 订单id | 员工id | 订单金额 | 状态 |
|---|---|---|---|
| 101 | 1 | 3000.00 | 已付款 |
| 102 | 1 | 2500.00 | 已付款 |
| 103 | 3 | 4000.00 | 已付款 |
| 104 | 2 | 1500.00 | 待付款 |
4 条全出来了。但老板只要已付款的。
三、第二步:筛选(WHERE)
SELECT订单id,员工id,订单金额,状态FROM订单表WHERE状态='已付款';| 订单id | 员工id | 订单金额 | 状态 |
|---|---|---|---|
| 101 | 1 | 3000.00 | 已付款 |
| 102 | 1 | 2500.00 | 已付款 |
| 103 | 3 | 4000.00 | 已付款 |
3 条,排除了订单 104(待付款)。老板想看金额从高到低。
四、第三步:分组+聚合(GROUP BY + COUNT/SUM)
需求里"每个员工的订单情况",就是按员工汇总。用 GROUP BY + 聚合函数:
SELECT员工id,COUNT(*)AS订单数,SUM(订单金额)AS总金额FROM订单表WHERE状态='已付款'GROUPBY员工id;| 员工id | 订单数 | 总金额 |
|---|---|---|
| 1 | 2 | 5500.00 |
| 3 | 1 | 4000.00 |
GROUP BY 员工id 把数据按员工合并——员工 1 有 2 笔已付款订单,合并成 1 行;员工 3 有 1 笔,也是 1 行。COUNT(*) 数行数,SUM(订单金额) 算总和。
这就是聚合查询的核心:明细变汇总,多行变一行。到这步,"每个员工的订单情况"已经出来了,但还没排序、还没取第一名。
五、第四步:排序+限制(ORDER BY + LIMIT)
需求后半句:"按总金额排序,取第一名。"在分组汇总的基础上排:
SELECT员工id,COUNT(*)AS订单数,SUM(订单金额)AS总金额FROM订单表WHERE状态='已付款'GROUPBY员工idORDERBYSUM(订单金额)DESCLIMIT1;| 员工id | 订单数 | 总金额 |
|---|---|---|
| 1 | 2 | 5500.00 |
员工 1 总金额 5500 最高,排第一,LIMIT 1 取第一条——这就是老板要的"每个员工已付款订单里,总金额第一名"。
注意 ORDER BY 用的是SUM(订单金额)而不是别名总金额——虽然 MySQL 里 ORDER BY 能用别名,但写全表达式更通用,换数据库也不报错。
六、HAVING 是干嘛的(知识扩展,原需求用不上但必学)
上面这个需求没用到 HAVING。但如果老板补一句"只要订单数至少 2 笔的员工",就需要在分组之后筛掉不满足的组——这时候用 HAVING:
SELECT员工id,COUNT(*)AS订单数,SUM(订单金额)AS总金额FROM订单表WHERE状态='已付款'GROUPBY员工idHAVINGCOUNT(*)>=2;| 员工id | 订单数 | 总金额 |
|---|---|---|
| 1 | 2 | 5500.00 |
员工 3 只有 1 笔,被 HAVING 过滤掉了。
WHERE 和 HAVING 的区别:WHERE 过滤行(分组前),HAVING 过滤组(分组后)。WHERE 用不了聚合函数,HAVING 可以。记住这个区别,遇到"分组后筛选"就想到 HAVING。
七、完整执行顺序速查卡(建议收藏)
上面的查询合在一起看:
SELECT员工id,COUNT(*)AS订单数,SUM(订单金额)AS总金额-- 5. 选列FROM订单表-- 1. 定表WHERE状态='已付款'-- 2. 过滤行GROUPBY员工id-- 3. 分组ORDERBYSUM(订单金额)DESC-- 4. 排序LIMIT1;-- 5. 限制SQL 的书写顺序和执行顺序不一样,这是新手最大的困惑来源:
| 顺序 | 关键字 | 作用 | 能用聚合函数 | 能用别名 |
|---|---|---|---|---|
| 1 | FROM | 确定从哪张表取数据 | 否 | 否 |
| 2 | WHERE | 过滤行(分组前) | 否 | 否 |
| 3 | GROUP BY | 分组 | 否 | 部分 |
| 4 | HAVING | 过滤组(分组后) | 是 | 否 |
| 5 | SELECT | 选列/聚合计算 | 是 | — |
| 6 | ORDER BY | 排序 | 是 | 是 |
| 7 | LIMIT | 限制行数 | 否 | 否 |
上面这个需求没用到 HAVING,但它排在 GROUP BY 之后、SELECT 之前,是 SQL 完整执行顺序的一环,记熟 F-W-G-H-S-O-L 就不会漏。
口诀:F-W-G-H-S-O-L(From → Where → GroupBy → Having → Select → OrderBy → Limit)。
记住这个顺序,写查询就不会懵——先想"从哪张表" → “过滤什么行” → “要不要分组” → “过滤什么组” → “选什么列” → “怎么排序” → “要几行”。
八、踩坑:SELECT 里写了不在 GROUP BY 里的列
-- 错:订单金额不是聚合列,也不在 GROUP BY 里SELECT员工id,订单金额,COUNT(*)AS订单数FROM订单表GROUPBY员工id;为什么报错?GROUP BY 把同一个员工的订单合并成一行,但"订单金额"既不在 GROUP BY 里、也不是聚合函数(比如 SUM)——一个员工有多笔订单,数据库不知道该取哪一笔的金额,没法合并,直接报错。
MySQL 严格模式下报1055(ER_WRONG_FIELD_WITH_GROUP),搜 “MySQL 1055” 或 “not in GROUP BY” 就能定位。非严格模式虽然不报错,但返回的订单金额是随机一笔,结果不可信——别侥幸。
铁规则:SELECT 里的非聚合列,必须出现在 GROUP BY 里。要么把列加到 GROUP BY,要么用聚合函数包起来(SUM、MAX 等)。
-- 对:订单金额用聚合函数包起来SELECT员工id,COUNT(*)AS订单数,SUM(订单金额)AS总金额FROM订单表WHERE状态='已付款'GROUPBY员工id;九、练习题(先自己写,再看答案)
题目 1:查出订单表中所有已付款订单,按订单金额从高到低排序。
题目 2:按员工分组,统计每个员工已付款订单的总金额和订单笔数。
题目 3:在题目 2 基础上,只显示总金额超过 3000 的员工。
题目 4:查出已付款订单中金额最高的那笔订单的 id 和金额。
参考答案
题目 1:
SELECT订单id,员工id,订单金额,状态FROM订单表WHERE状态='已付款'ORDERBY订单金额DESC;-- 结果:103(4000) → 101(3000) → 102(2500)题目 2:
SELECT员工id,COUNT(*)AS订单数,SUM(订单金额)AS总金额FROM订单表WHERE状态='已付款'GROUPBY员工id;-- 结果:员工1→2单5500,员工3→1单4000题目 3:
SELECT员工id,COUNT(*)AS订单数,SUM(订单金额)AS总金额FROM订单表WHERE状态='已付款'GROUPBY员工idHAVINGSUM(订单金额)>3000;-- 结果:员工1→5500,员工3→4000(都超过3000)题目 4:
SELECT订单id,订单金额FROM订单表WHERE状态='已付款'ORDERBY订单金额DESCLIMIT1;-- 结果:订单103,4000.00你写查询的习惯是什么?
你写 SQL 的时候,是先写 SELECT 还是先写 FROM?我带过的人里一半先写 SELECT(想着要什么列),一半先写 FROM(想着从哪取数)。
我个人习惯先写 FROM 和 WHERE——先把数据范围圈出来,再想选什么列。评论区聊聊你的顺序。
从零数据分析· 十年数据分析经验 · 实战笔记
SQL 从入门到精通 28 篇持续更新中,每篇附标准练习数据,复制即运行。
关注我,把 SQL 学成肌肉记忆。
📋 系列目录:零基础学SQL:从入门到精通完整目录(28篇持续更新)
📖 上篇:篇07 COUNT统计总对不上?聚合函数和NULL的坑一图搞懂
附录:标准练习数据(复制即可运行,全系列通用)
-- 全系列通用四张表,每次运行先 DROP 再 CREATE,不会报"表已存在"DROPTABLEIFEXISTS员工表;CREATETABLE员工表(员工idINTPRIMARYKEY,姓名VARCHAR(20),部门VARCHAR(20),工资DECIMAL(10,2),邮箱VARCHAR(50),手机VARCHAR(20),入职日期DATE);INSERTINTO员工表(员工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');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','zhangsan@demo.com','北京市朝阳区'),(2,'李四','13900000002','lisi@demo.com','上海市浦东新区'),(3,'王五','13900000003','wangwu@demo.com','广州市天河区');DROPTABLEIFEXISTS任务表;CREATETABLE任务表(任务idINTPRIMARYKEY,员工idINT,备注VARCHAR(100),状态VARCHAR(20));INSERTINTO任务表(任务id,员工id,备注,状态)VALUES(1,1,'完成需求评审','已完成'),(2,2,NULL,'进行中'),(3,3,'修复线上bug','已完成'),(4,4,NULL,'待分配');