讲解
上一章的聚合是对整张表算一个总数,而真实的问题往往是「每种状态的订单各有多少」「每个城市各有多少学生」——按某个维度把行分成若干组,每组各算一份聚合值,这就是 GROUP BY。SELECT status, COUNT(*) FROM orders GROUP BY status 会按订单状态分成三组,每组返回一行:状态值加上该组的统计。
GROUP BY 的用法规则一句话:SELECT 列表里出现的普通列,必须出现在 GROUP BY 里(或者被聚合函数包起来)。因为分组后每组只产生一行,没分组又没聚合的列「不知道取哪个值」,主流数据库会直接报错。可以多列分组:GROUP BY city, class_name 按城市和班级的组合分组。
理解执行顺序很重要:FROM → WHERE → GROUP BY → 聚合计算 → SELECT。WHERE 在分组之前过滤行,所以 WHERE 里不能用聚合函数,也不能引用分组后才有意义的条件。WHERE status != 'cancelled' GROUP BY class_name 是合法的(先排除取消的订单再分组);想按「组内统计值」过滤(如「人数超过 1 的城市」)则要用下一章的 HAVING。
示例
每种订单状态的单数与总额:
SELECT status, COUNT(*) AS order_count, SUM(amount) AS total FROM orders GROUP BY status ORDER BY total DESC;
每个城市有多少学生(NULL 城市自成一组):
SELECT city, COUNT(*) AS student_count FROM students GROUP BY city ORDER BY student_count DESC;
先筛选再分组:只统计已支付订单,看每个学生花了多少钱:
SELECT student_id, COUNT(*) AS paid_orders, SUM(amount) AS paid_total FROM orders WHERE status = 'paid' GROUP BY student_id ORDER BY paid_total DESC;
常见坑
- SELECT 里放了未分组的普通列:SQLite 会「宽容」地从组里随便取一个值给你,MySQL/PostgreSQL 直接报错——无论哪种都是 bug。规则:普通列要么进 GROUP BY,要么进聚合函数。
- 以为 GROUP BY 会自动排序:分组输出的顺序是不保证的,很多情况下恰好按分组键排列只是实现巧合。要顺序就写 ORDER BY。
- 把行级条件写进分组后的思维里:「金额大于 50 的订单按状态分组」的条件 amount > 50 是行级条件,属于 WHERE;写在 HAVING 里语义就变了(变成对分组键过滤)。行级条件放 WHERE,组级条件放 HAVING。
- NULL 分组被忽略:GROUP BY city 会把所有城市为 NULL 的行聚成一组,这在统计「未填写人数」时有用,但也容易被当成「脏数据」忽视。报表里最好 COALESCE(city, '未知') 让这组显式可见。
小结
GROUP BY 按维度分组、每组一行聚合结果;普通列必须分组或聚合;WHERE 在分组前过滤行。下一节学习对分组结果再筛选:HAVING。