group by 是压缩而非筛选,执行后原始行消失;应按需求选择:汇总用 group by,明细加统计用窗口函数。

GROUP BY 本身就会丢明细——它不是“筛选”,而是“压缩”。你看到的不是原始行,是每组聚合后的一行结果。
GROUP BY 执行后原始行就没了
数据库执行顺序是 FROM → WHERE → GROUP BY → HAVING → SELECT。也就是说,GROUP BY 一完成,几十万条订单就变成几十行分组统计了,order_id、created_at 这些字段在内存里已经不可见。你 SELECT 的 name 或 status 如果没进 GROUP BY,又没套聚合函数,MySQL 就只能随机挑一条值塞进去——这不是 bug,是语义缺失下的妥协行为。
- 你查“每个部门平均薪资”,却在 SELECT 里多写了
employee_name:数据库根本没法回答“该取张三还是李四的名字” - 旧版 MySQL 可能返回某条记录的
employee_name,但换一次查询、换一个索引、升级版本,结果可能就变了 - PostgreSQL/SQL Server 直接报错:
column "employee_name" must appear in the GROUP BY clause,反而更诚实
想保留明细?别硬扛 GROUP BY,用窗口函数
要同时看到每条订单,又知道“这个用户总共花了多少钱”,GROUP BY 不是解法,SUM() OVER (PARTITION BY user_id) 才是。
-
SUM(amount) OVER (PARTITION BY user_id):每行都带本用户的总金额,行数不变 -
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC):给每个用户的订单按时间倒序编号,再WHERE rn = 1就能取最新单——不用先 GROUP 再 JOIN 回去 - 注意:窗口函数不能直接出现在
WHERE里,必须套一层子查询或 CTE,否则报invalid reference to FROM-clause entry - MySQL 8.0+、PostgreSQL、SQL Server 全支持;SQLite 3.25+ 也行,但老版本不认
OVER
非得用 GROUP BY 且要“还原”某个字段?明确告诉数据库你要什么
如果业务上真需要把一组里的多个 order_id 拼成字符串展示(比如后台审核页),就别依赖随机值,用聚合函数显式表达意图:
-
GROUP_CONCAT(order_id SEPARATOR ','):MySQL 专属,注意默认长度限制是 1024,超长会截断,需调group_concat_max_len -
STRING_AGG(order_id, ','):PostgreSQL 写法,支持ORDER BY控制拼接顺序 -
MAX(order_id)或MIN(order_id):如果只关心“最早/最晚单号”,语义清晰,无歧义 - 避免
ANY_VALUE(order_id):它不保证稳定性,仅用于调试或确认字段确实无关紧要时
真正容易被忽略的,不是语法怎么写,而是没想清楚自己到底要什么:是要“按组看汇总”,还是“看每条记录的同时知道它所属组的统计值”。前者用 GROUP BY,后者必须绕开它——窗口函数不是高级技巧,是逻辑分水岭。










