按表达式分组是什么
GROUP BY 的分组键不限于列,也可以是一段表达式:按天、按月、按函数处理后的结果分组都是常见需求。代价是表达式分组一般用不上普通列索引,要靠改写条件、生成列或表达式索引来救,否则每次统计都得扫全表再排序。
基本信息
| 支持范围 | MySQL 8 与 PostgreSQL 都允许分组键是任意表达式 |
|---|---|
| 序号简写 | 两库都支持按输出列序号分组,如 GROUP BY 1 |
| 索引 | 表达式分组一般无法直接使用普通列索引 |
| 优化手段 | MySQL 8.0.13+ 用表达式索引或生成列;PostgreSQL 建表达式索引 |
| 时区 | 按天分组前先统一时区,否则日界会漂移 |
| 过滤位置 | 过滤原始行用 WHERE,过滤分组结果用 HAVING |
MySQL 按天统计
MySQL 按天统计
SELECT DATE(created_at) AS day,
COUNT(*) AS orders, -- 当天订单数
SUM(amount) AS amount_sum -- 当天金额合计
FROM orders
WHERE created_at >= '2024-01-01' -- 先按列做范围过滤,保住索引
AND created_at < '2024-02-01'
GROUP BY DATE(created_at) -- 分组键是表达式
ORDER BY day; -- 不写 ORDER BY 结果顺序不保证PostgreSQL 按月与表达式索引
PostgreSQL 按月与表达式索引
SELECT date_trunc('month', created_at) AS mon,
COUNT(*) AS orders
FROM orders
GROUP BY 1 -- 按输出列序号分组
ORDER BY mon;
-- 建表达式索引,让上面的按月分组与排序能走索引
CREATE INDEX idx_orders_mon ON orders (date_trunc('month', created_at));常见分组键写法
| 需求 | MySQL 8 | PostgreSQL |
|---|---|---|
| 按天 | GROUP BY DATE(col) | GROUP BY col::date 或 date_trunc('day', col) |
| 按月 | GROUP BY DATE_FORMAT(col,'%Y-%m') | GROUP BY date_trunc('month', col) |
| 按序号 | 支持 GROUP BY 1 | 支持 GROUP BY 1 |
| 函数索引 | 8.0.13+ 表达式索引或生成列 | CREATE INDEX ... (表达式) |
| 结果顺序 | 分组后顺序不保证 | 同样需要显式 ORDER BY |
典型使用场景
- 按天、按周、按月出趋势报表
- 按金额区间分桶,统计订单分布
- 按字符串处理结果分组:域名后缀、手机号号段、去空格后的名称
- 按业务计算字段分组,如毛利档位、评分区间
- 预处理宽表:先把常用口径聚合好,上层直接查
- 数据质量排查:按归一化后的键分组,看是否存在重复主体
GROUP BY 与 ORDER BY 里引用的表达式要完全一致:MySQL 可以按别名排序,PostgreSQL 可以按输出列序号分组,跨库脚本写明完整表达式比依赖简写更稳妥。
WHERE 里对列做函数会让索引用不上:WHERE DATE(created_at) = '2024-06-01' 改成范围条件 created_at >= '2024-06-01' AND created_at < '2024-06-02' 才能走索引。