用group by+聚合函数可实现简单报表,如按日统计订单需用date()归一时间粒度;分类汇总需驱动表左连接补零;多维交叉报表用多字段group by并规范排序;业务口径确认比sql编写更关键。

直接用 GROUP BY + 聚合函数就能搞定大多数简单报表,不需要额外工具或存储过程。
按日期统计每日订单金额和数量
这是最典型的日报类需求,关键在于把时间字段归一到“日”粒度。MySQL 的 DATE() 函数比 STR_TO_DATE() 或字符串截取更可靠,尤其当字段是 DATETIME 类型时。
- 错误写法:
GROUP BY SUBSTRING(create_time, 1, 10)—— 字符串操作依赖格式,且无法走索引 - 正确写法:
GROUP BY DATE(create_time),配合WHERE create_time >= '2024-01-01'可命中索引 - 注意
create_time字段类型:如果是VARCHAR存时间,先用STR_TO_DATE(create_time, '%Y-%m-%d %H:%i:%s')转换再DATE(),否则聚合结果可能为空
SELECT DATE(create_time) AS report_date, COUNT(*) AS order_count, SUM(amount) AS total_amount FROM orders WHERE create_time >= '2024-01-01' GROUP BY DATE(create_time) ORDER BY report_date;
分类汇总带空值/零值补全(如缺货品类)
原始 GROUP BY 只返回有数据的分组,但报表常需“所有品类都列出,没销量也显示 0”。这时不能靠 COALESCE(SUM(...), 0) 解决,得用驱动表左连接。
- 先建一个品类维度表(哪怕只是临时
VALUES行列),例如:(VALUES ROW('手机'), ROW('电脑'), ROW('配件')) AS categories(cat_name) - 再
LEFT JOIN订单表,SUM(orders.amount)自然为NULL,用IFNULL(..., 0)替换 - 避免在
WHERE中对右表字段过滤(如WHERE orders.status = 'paid'),这会让左连接退化为内连接;应把条件移到ON子句里
SELECT
c.cat_name,
IFNULL(SUM(o.amount), 0) AS sales
FROM (VALUES ROW('手机'), ROW('电脑'), ROW('配件')) AS categories(cat_name) AS c
LEFT JOIN orders o
ON c.cat_name = o.category
AND o.status = 'paid'
GROUP BY c.cat_name;
多维度交叉报表(如地区 × 月份销售)
GROUP BY 支持多个字段,但行列转换得靠应用层或 MySQL 8.0+ 的 PIVOT 等价写法(实际用 CASE WHEN + 聚合模拟)。重点是别让 GROUP BY 字段顺序影响可读性。
- 地区放前、月份放后,结果按地区主序、月份次序排列,更符合阅读习惯
- 月份建议用
DATE_FORMAT(create_time, '%Y-%m')而非YEAR(create_time), MONTH(create_time),避免跨年排序错乱(比如 2023-12 排在 2024-01 前) - 如果维度超过 3 个(比如加“支付方式”),结果集会迅速变宽,此时更适合导出后用 Excel 透视,而非硬写 SQL
SELECT region, DATE_FORMAT(create_time, '%Y-%m') AS ym, SUM(amount) AS total FROM orders GROUP BY region, DATE_FORMAT(create_time, '%Y-%m') ORDER BY region, ym;
真正麻烦的不是写 SQL,而是确认业务口径:比如“销售额是否含退款”“用户数是去重还是累计”——这些必须和产品对齐,SQL 只是忠实执行规则。字段含义模糊、状态定义不一致,比语法错误更难 debug。











