最稳妥的方式是用 SUM(CASE WHEN ... THEN 1 ELSE 0 END) 统计各订单状态数量,避免 COUNT(CASE ...) 因忽略 NULL 导致漏计;需确保 status 字段有索引,分组统计时正确使用 GROUP BY。

用 CASE WHEN + SUM 统计多个订单状态数量
直接在一条 SELECT 里统计多个状态(比如待支付、已发货、已完成)的数量,最稳妥的方式是用 CASE WHEN 配合 SUM。比写多个子查询或 UNION 效率高,也比 COUNT(CASE ...) 更不容易漏掉 NULL 状态。
常见错误是误用 COUNT:它会忽略 NULL,而 CASE 没匹配到时默认返回 NULL,导致该状态计数为 0 被跳过——看起来像“没统计到”,其实是被 COUNT 过滤掉了。
-
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END)—— 安全,0 和 1 都计入求和 -
COUNT(CASE WHEN status = 'pending' THEN 1 END)—— 危险,不匹配时返回NULL,COUNT不统计它 - 如果表里有
status为NULL的脏数据,建议先加WHERE status IS NOT NULL或在CASE中显式处理
GROUP BY 不是必须的,但得看你要聚合的维度
如果只是算全表各状态总数,不需要 GROUP BY;但如果要按日期、用户等级、店铺等分组统计,就必须加 GROUP BY,且所有非聚合字段都得出现在 GROUP BY 列表里。
容易踩的坑是:在 MySQL 5.7+ 严格模式下,漏写 GROUP BY 会导致报错 ERROR 1055;在旧版或兼容模式下可能返回不确定结果——比如某条记录的 shop_name 值是随机选的,不是你预期的。
- 按天统计:
SELECT DATE(created_at) AS day, SUM(CASE WHEN status='shipped' THEN 1 ELSE 0 END) AS shipped_cnt FROM orders GROUP BY DATE(created_at) - 跨多张表关联时,确保
GROUP BY字段来自主表或明确别名,避免歧义 - 别在
SELECT里写未聚合也未出现在GROUP BY中的字段,比如user_id—— 除非你真想看“每组随便一个 user_id”
用 PIVOT(仅 SQL Server / Oracle)还是兼容写法?
SQL Server 和 Oracle 支持 PIVOT 语法,看着更紧凑,但可读性差、调试难,而且 MySQL、PostgreSQL、SQLite 都不支持。线上项目优先选兼容写法。
比如 SQL Server 的 PIVOT 写法:
SELECT * FROM (SELECT status FROM orders) t PIVOT(COUNT(*) FOR status IN ([pending],[shipped],[done])) p
表面简洁,但一旦加条件(如限定时间范围)、加其他字段(如渠道来源),就得嵌套多层子查询,很快失控。而 CASE WHEN 方案可以自然叠加 WHERE、JOIN、额外聚合字段,扩展性明显更好。
- MySQL 用户别折腾
PIVOT模拟,徒增复杂度 - 如果团队有 DBA 强推
PIVOT,确认所有下游系统(BI 工具、ORM)是否能正确解析生成的列名 - 注意
PIVOT的IN列表必须是字面量,不能是子查询或变量——动态状态列表根本没法用
性能关键:状态字段一定要有索引
当订单表超百万行,没索引的 status 字段会让这种统计变慢——执行计划大概率走全表扫描。即使只查几个状态值,优化器也很难利用无索引的等值条件快速定位。
复合索引更优,尤其是常和时间范围一起查的场景:
- 单列索引够用:
CREATE INDEX idx_orders_status ON orders(status) - 高频按时间+状态查:
CREATE INDEX idx_orders_status_created ON orders(status, created_at)—— 注意顺序:等值条件字段(status)放前面 - 如果经常
WHERE status IN ('pending','shipped') AND created_at > '2024-01-01',这个复合索引能覆盖索引扫描,避免回表 - 别给低区分度字段(比如只有 3–5 个值的状态)建位图索引(Oracle)或全文索引,意义不大
状态字段值少、更新频繁时,索引维护成本低,建索引几乎只有收益。

















