导读:本期聚焦于小伙伴创作的《SQL怎么统计不同状态的数量?条件聚合实战解析》,敬请观看详情。订单表里有待支付、已发货、已完成、已取消等多种状态,写多条查询再union不仅慢还难维护。条件聚合用sum加case when在单条SQL里完成分组计数,数据库只扫一遍表。把case when包在聚合函数内,满足某状态返回1否则0,求和即得该状态数量。还能结合where过滤时间区间,或用count区分空值。掌握这套写法,报表统计、看板指标都能用一条语句算出各状态占比,避免重复代码和性能浪费。

在业务系统里,我们经常会遇到一张表同时记录多种状态的数据,比如订单有待支付、已发货、已完成、已取消等。如果想知道每种状态分别有多少条记录,最直观的思路是写多条查询,或者用group by状态字段。但在实际报表场景中,往往需要将不同状态的数量作为并列字段输出,这时候条件聚合就是最优雅的解决方案。

SQL怎么统计不同状态的数量?条件聚合实战解析

什么是条件聚合

条件聚合指的是在聚合函数(如sum、count、avg)内部嵌入条件判断,通常配合case when表达式使用。它的核心思想是:在一条SQL中,根据某一列的值是否满足指定条件,决定该行是否参与当前聚合计算。以统计状态数量为例,我们可以让sum函数在状态匹配时加1,不匹配时加0,从而得到该状态的记录数。

这种方式与先group by再外层拼装相比,最大优势是只扫描一次数据表。数据库引擎在执行时,对每一行做一次case when判断并累加,无需临时分组和多次回表。对于百万级数据的报表查询,性能差异非常明显。同时,条件聚合让结果以宽表形式呈现,每一个状态对应一个字段,前端直接取值即可,不需要再做行列转换。

基础实战:单表多状态计数

假设有一张订单表orders,包含字段id、status、created_at。status取值为:pending(待支付)、shipped(已发货)、finished(已完成)、canceled(已取消)。我们要统计各状态的数量,用条件聚合可以写成如下SQL:

select
  sum(case when status = 'pending' then 1 else 0 end) as pending_cnt,
  sum(case when status = 'shipped' then 1 else 0 end) as shipped_cnt,
  sum(case when status = 'finished' then 1 else 0 end) as finished_cnt,
  sum(case when status = 'canceled' then 1 else 0 end) as canceled_cnt,
  count(*) as total_cnt
from orders;

上面这段SQL中,每一行数据都会经过四个case when判断。例如某行status是shipped,那么shipped_cnt对应的表达式返回1,其余三个返回0,最终sum汇总出各自的总数。count(*)则统计总行数,方便计算占比。

这种写法的可扩展性很好。如果新增了状态refunding(退款中),只需要在select列表里加一行sum(case when status = 'refunding' then 1 else 0 end) as refunding_cnt即可,底层扫描逻辑完全不变。比起分别写四条select count(*) where status = ?再join,代码量少且易于阅读。

结合过滤与分组的高级用法

条件聚合不仅能用在没有group by的整表统计中,也可以和group by配合,按维度(如日期、地区)分别统计状态。例如按天统计每日各状态订单量:

select
  date(created_at) as order_date,
  sum(case when status = 'pending' then 1 else 0 end) as pending_cnt,
  sum(case when status = 'finished' then 1 else 0 end) as finished_cnt,
  sum(case when status = 'canceled' then 1 else 0 end) as canceled_cnt
from orders
where created_at >= '2023-01-01'
group by date(created_at)
order by order_date;

这里where子句先过滤了时间范围,减少参与计算的数据量;group by按天分组后,每个分组内部再走条件聚合逻辑。这样可以直接生成看板所需的每日趋势数据。注意,在MySQL中date()函数提取日期部分,其他数据库可用trunc或cast替代。

有时候我们还需要计算状态占比,可以在同一层SQL中嵌套使用。比如已完成率:sum(case when status='finished' then 1 else 0 end) * 1.0 / count(*)。乘1.0是为了把整数除法转为浮点,避免只得到0或1。如果数据中存在null状态,case when默认null不参与sum,所以不用担心脏数据干扰。

count与sum的选择细节

除了sum(case when ... then 1 else 0 end),也有人写count(case when status='finished' then 1 else null end)。两者结果一致,但原理略有不同:count只统计非null值,所以当条件满足时给1(非null)就会被计入;不满足给null就不计。sum则是把1和0相加。从执行计划看,sum在多数引擎中略快,因为不需要处理null判断逻辑。

-- 两种写法对比
select
  count(case when status = 'finished' then 1 else null end) as cnt_by_count,
  sum(case when status = 'finished' then 1 else 0 end) as cnt_by_sum
from orders;

在使用count写法时,务必把else分支写成null而不是0。如果写成else 0,那么不满足条件的行也会返回0,count(0)依然会计数,导致结果变成总行数。这是一个非常常见的错误,调试时往往发现所有状态数量都等于总记录数,就是因为这个细节。

另一个注意点是,当状态字段本身可能为null时,case when status = 'xxx'在status为null时自然走到else分支,不会报错。但如果用count(status)来试图统计某状态,null值行会被直接忽略,和条件聚合的语义不同,需要按业务确认是否要包含null状态。

性能与索引建议

条件聚合虽然只扫一次表,但如果表很大,依然建议对过滤列和状态列建立复合索引。以上面按天统计为例,在(created_at, status)上建索引,可以让where范围和group by都命中索引,部分数据库甚至能走索引覆盖,完全不回表。执行explain时,应关注rows和extra字段,确认没有using filesort或临时表。

如果状态枚举固定,还可以考虑将status设为枚举类型或小的字典表外键,减少存储和比较开销。对于超高并发的实时统计,条件聚合SQL可做成物化视图或定时跑批写入统计表,避免每次请求都算一遍。总体来看,条件聚合是兼顾开发效率和运行效率的优选方案,比动态拼SQL或应用层循环查询要可靠得多。

常见误区总结

不少人在初学时会把条件聚合和行转列混淆。条件聚合是在行层面做判断后聚合成列;而行转列(pivot)通常是数据库专属语法,可读性因引擎而异。标准SQL的条件聚合跨数据库通用,迁移成本低。另外,不要为了“优雅”把过多状态塞进一个查询,若状态超过十五个,select列表会很长,此时可考虑用group by status常规写法,应用层再映射,保持SQL可维护性。

条件聚合的本质,是用表达式控制聚合函数的输入值,从而在同一分组内算出多个维度的指标。

掌握了上述写法,你在写报表、做数据看板、输出业务日报时,都可以用一条SQL把不同状态的数量、比例、趋势一次性查出来。这比分散查询再拼装更不容易出错,也更容易让其他开发者看懂你的统计逻辑。

SQL条件聚合状态统计修改时间:2026-08-04 05:30:14

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。