本文依赖第 01 篇的
mysql_zero_to_one数据库,请先跑完建表和插入数据的脚本。
一、聚合函数:对一堆数据做统计
什么是聚合?
普通的 SELECT 是”一行一行地返回数据”。聚合函数是”把很多行压成一行,得出统计结果”。
比如你想知道:总共有多少订单?总销售额是多少?——这就要用聚合。
1 | -- 统计已支付订单的数量、总额、均值、最大最小订单 |
预期结果:一行数据,包含所有已支付订单的统计信息。
注意:
COUNT(*)统计所有行;COUNT(列名)会跳过 NULL 值,两者有区别。
分组统计:GROUP BY
“每个用户分别买了多少?”——需要按用户分组后再统计:
1 | -- 按用户分组统计,且只看至少下了 1 笔已支付订单的用户 |
预期结果:每个有已支付订单的用户各占一行。
WHERE vs HAVING 傻傻分不清?
WHERE:分组之前过滤,过滤的是原始行HAVING:分组之后过滤,过滤的是每组的汇总结果
口诀:WHERE 管原始数据,HAVING 管统计结果。
二、多表连接:把多张表拼在一起
为什么需要多表连接?
我们的订单表里只存了 user_id,并没有直接存用户名。想在订单结果里看到用户名,就需要把 orders 和 users 两张表”拼”在一起——这就是 JOIN。
INNER JOIN:只要两边都有的
1 | -- 查询已支付订单,同时显示用户名 |
INNER JOIN 的逻辑:只返回两张表里都能匹配上的行。如果订单表有一个 user_id 在用户表里不存在,这行订单就不会出现在结果里。
LEFT JOIN:左表全部保留
1 | -- 查询所有用户,以及他们的订单数(没下过单的用户也要显示) |
预期结果:所有用户都显示出来,carol 没有订单,她的”订单数”为 0。
LEFT JOIN 的关键:左边的表(users)每行都会保留;右边的表(orders)找不到匹配时用 NULL 填充。
常见 bug:在 LEFT JOIN 之后的 WHERE 里过滤右表的列,就会把 NULL 的行过滤掉,LEFT JOIN 变成了 INNER JOIN。正确做法是在
ON里写右表条件,或者在 HAVING 里过滤。
SELF JOIN:自己连接自己
当一张表里有”上下级关系”时用到,比如员工和上级都在同一张表里:
1 | -- 建一个员工表,manager_id 指向同表的 employee_id |
预期:周经理的”直属上级”为 NULL,李和王的上级都是周经理。
三、子查询:查询里套查询
子查询就是把一个 SELECT 结果作为另一个 SELECT 的条件或数据源。
1 | -- 找出金额高于平均订单金额的订单 |
括号里的查询先执行,得出平均值,外层再用这个值过滤。
EXISTS:判断”有没有”,不在乎具体值
1 | -- 找出至少下过一笔已支付订单的用户 |
EXISTS 只关心括号里的查询有没有结果,SELECT 1 里的 1 只是占位,写什么都行。
1 | -- 找出从来没下过已支付订单的用户 |
EXISTS vs IN 怎么选?
IN适合子查询返回少量固定值,写起来直观EXISTS适合子查询和外层有关联、数据量大的场景- 现代 MySQL 优化器已经很智能,实际性能差异要用
EXPLAIN看,不能凭感觉
四、窗口函数:既要统计,又要保留明细
普通 GROUP BY 的局限
GROUP BY 统计之后,原始行就消失了,每组只剩一行汇总。但有时候我们想要:每个用户的订单按金额排名,同时还要保留每条订单的详情——这就是窗口函数解决的问题。
1 | -- 为每个用户的订单按金额从高到低排名 |
ROW_NUMBER vs RANK 的区别:
ROW_NUMBER:1、2、3、4——即使金额相同也给不同排名RANK:1、2、2、4——相同金额并列,下一个跳过数字
LAG / LEAD:看上一行和下一行
1 | -- 对比同一用户相邻两笔订单的金额变化 |
预期:第一笔订单的”上一笔金额”为 NULL(没有上一笔),最后一笔的”下一笔金额”也是 NULL。
常见误区提醒
- HAVING 写成 WHERE:
WHERE COUNT(*) > 1会报错,聚合条件只能写 HAVING。 - LEFT JOIN 之后在 WHERE 里过滤右表:
WHERE o.order_id IS NOT NULL会让 LEFT JOIN 失效,变成 INNER JOIN。 - 以为窗口函数的 ORDER BY 就是最终排序:窗口函数里的 ORDER BY 只是定义窗口内的顺序,结果集不一定按这个顺序展示,最外层要另加 ORDER BY。
本章核心总结
- 聚合:把多行压成一行做统计;
WHERE在前、HAVING在后 - JOIN:拼接多张表;
LEFT JOIN保留左表所有行;ON 里写关联条件 - 子查询:查询中嵌套查询;
EXISTS用来判断”有没有” - 窗口函数:在保留明细的同时做分组统计,MySQL 8.0 才完整支持
下一步学习建议
下一篇进入事务,学习”如何保证多条 SQL 要么全部成功、要么全部撤销”——这是做任何业务系统必须掌握的核心能力。