这周带新人调一张报表,SQL写得不算复杂,就是三张表关联后按部门算平均工资。结果一看,技术部门的人均月薪跑出来八十多万,明显不符常理。一开始我还怀疑数据有问题,单独跑每张表行数排查才发现,问题出在两张大表之间存在一对多关系,JOIN 完之后中间结果行数翻了好几倍,后面的 AVG 自然被整体撑大。
这种问题放在“MySQL 基本查询”系列里特别典型:很多人单表 WHERE、ORDER BY 用得溜,一遇到分组统计、关联查询、子查询就开始放飞自我。这篇是第 3 篇,我会集中写清楚平时项目里绕不开的几块内容:GROUP BY 分组聚合、JOIN 关联查数据时怎么防止数据被放大、三种子查询的写法与坑,以及排序和 LIMIT 分页中容易踩的两个线上问题。最后再把 SQL 的书写顺序和执行顺序放在一起讲,让你排查慢查询时能有个方向。
1. 统计需求为什么不能只靠单表扫一眼
1.1 从“找数据”到“算账”,查询思路要换挡
前两篇聊的基本查询,本质上是在做一件事:从表里把符合条件的行检索出来。写 SELECT 指定列、用 WHERE 过滤行、ORDER BY 排序,这些操作解决的是“把某几条数据找出来”的问题,属于一行一行处理数据的思维。
实际业务里更常见的需求是算账,比如“每个部门有多少人”“各个状态订单的总金额是多少”“最近一年每个月的注册用户数”。这类需求的共同点在于,不能再把眼睛盯在某一行上,而是要把很多行划成不同的组,然后对每一组做汇总计算,这个动作在 MySQL 里对应的核心语法就是 GROUP BY,搭配 COUNT、SUM、AVG、MAX、MIN 这些聚合函数一起使用。
如果你只会写“找数据”的查询,遇到统计需求时容易用最笨的办法:先把明细查出来,拉到 Java、Python 或者 Excel 里再用循环累加。数据量小的时候没毛病,十几行数据怎么折腾都行。可一旦上了十万、百万行,把大量明细从数据库拖到应用层做统计,既浪费网络带宽和内存,又慢得让页面直接超时。SQL 里的分组聚合能力,就是为了让你把统计计算下推到数据库引擎,在数据所在的地方直接完成汇总。
1.2 一个全员统计报表跑挂的现场
我开头提到的那个报表坑,拆开看其实很典型。当时的业务背景是统计每个部门的平均工资,比较特殊的是工资变动记录单独存了一张表,一个人可能有多条调薪记录。
他写的 SQL 逻辑大致是:查出员工基础信息,和调薪记录表直接做关联,再按部门 GROUP BY 算平均工资。问题就出在这里。员工表和调薪记录表是典型的一对多关系,比如张三有两条调薪记录,关联后张三会变成两行,部门里每个人的权重被重复计算。如果其他员工只有一条记录,而某几个核心员工有七八条历史调薪记录,他们会对部门平均值产生不成比例的影响。
把这种查询简化成代码如下:
sql复制-- 错误示范:先关联后聚合,明细被放大后 AVG 结果失真
SELECT d.dept_name,
AVG(e.salary) AS avg_salary
FROM departments d
LEFT JOIN employees e ON d.dept_id = e.dept_id
LEFT JOIN salary_log s ON e.emp_id = s.emp_id
GROUP BY d.dept_name;
表面上看三张表都关联上了,语法没问题,执行也不报错。但 salary_log 表里每个员工可能对应多条工资调整记录,连完之后员工记录出现重复,AVG 就算错了。正确写法是先把 salary_log 这个明细表按员工维度聚合成当前工资,再回到员工表和部门表做关联,或者干脆用子查询先把多行压缩成一行。
所以从这个案例能看出,写统计类 SQL 之前,一定要先想清楚一件事:当前查询的数据粒度是哪一层。粒度理解错了,后面所有写法全白搭。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. GROUP BY 分组聚合:最基础也最容易写错
2.1 用快递分拣的模型理解 GROUP BY
很多人觉得 GROUP BY 很难,其实在生活里天天见。快递分拣员把全国寄来的包裹按省份丢进不同格口,每个格口里堆着几十上百个包裹。分组之后,你只能针对每个格口问“格口里有多少个包裹”“总重量是多少”“最大包裹多重”,如果非要问“上海这个格口里具体哪一个包裹是寄给张三的”,当场就抓瞎了,因为所有上海的包裹已经混在一起了。
GROUP BY 干的就是分拣这件事。指定 department_id 作为分组字段后,同一个部门的员工被放进同一个分组,接下来 SELECT 的聚合函数就是对每个分组统一计算。假如 SELECT 后面出现了一个普通字段 employee_name,同时又没有对这个字段做任何聚合处理,逻辑上就出问题了:分组里有二十个员工,数据库到底该返回哪一个姓名?MySQL 的 only_full_group_by 模式下会直接报错,写不了这种语句,但就算你在旧版本或关闭这个模式下能跑,返回的结果也是随机取一条,完全没有业务意义。
顺手说一句,我见过不少老项目把 only_full_group_by 关掉,理由是“以
