MySQL中的聚合函数用于对一组值执行计算并返回单个汇总结果,是数据分析与报表统计的核心工具。常见的聚合函数包括 count、sum、avg、max 和 min,它们通常配合 group by 子句实现分组统计,也可以单独使用来统计整张表的概况。理解聚合函数的工作机制,能帮助我们写出正确且高效的SQL语句。
一、常用聚合函数基础用法
在不使用 group by 的情况下,聚合函数会针对查询结果集的所有行进行计算,最终只返回一行汇总数据。例如,统计用户表中总人数、平均年龄以及最高积分,可以通过如下语句实现。
需要注意的是,count(*) 会统计包括 null 值在内的所有行,而 count(列名) 只统计该列非 null 的行数。在业务统计中,如果某些字段允许为空,选错计数方式会导致数据偏差。sum 和 avg 在计算时也会自动忽略 null 值,但不会把 null 当作 0 处理。
-- 统计用户总体情况 select count(*) as total_users, avg(age) as avg_age, max(score) as max_score, min(score) as min_score from user_info;
1.1 count 的三种常见形式
count(*) 统计行数,效率通常最高,因为不需要读取具体列值。count(列) 会跳过该列为 null 的记录,适合统计有效数据量。count(distinct 列) 则用于统计不重复值的数量,在去重报表中非常实用,但会带来额外的排序或哈希开销。
下面示例统计了不同维度的数量,可以直观看到它们的差异。当表数据量大时,count(distinct) 可能成为性能瓶颈,需要结合业务评估是否必要。
select count(*) as all_rows, count(email) as has_email, count(distinct city) as city_count from user_info;
二、group by 分组聚合
group by 子句用于将结果集按一个或多个列进行分组,然后对每个组分别执行聚合计算。分组后,select 中出现的非聚合列必须包含在 group by 中,否则 MySQL 在非严格模式下会随机取一行的值,在严格模式下则直接报错。
以下示例按城市统计用户数与平均积分,能够清晰展现各地活跃度。分组列的顺序会影响中间结果的组织方式,但不影响最终逻辑结果,只是某些情况下对索引利用有细微差别。
select city, count(*) as user_count, avg(score) as avg_score from user_info group by city;
2.1 多列分组与排序
当业务需要更细的维度时,可以使用多列分组。例如先按省份再按城市统计,SQL 会先按第一列分组,再在组内按第二列继续拆分。配合 order by 可以控制输出顺序,让报表更易读。
多列分组要注意索引覆盖,如果分组字段有联合索引,MySQL 可能使用松散索引扫描来避免临时表和文件排序,大幅提升性能。缺乏合适索引时,分组操作会生成内部临时表,数据量大时响应明显变慢。
select province, city, count(*) as cnt from user_info group by province, city order by province, cnt desc;
三、where 与 having 的区别
where 在聚合之前过滤原始行,减少参与计算的数据量,因此应尽量把能确定的条件写在 where 中。having 则在 group by 之后对分组结果进行筛选,可以使用聚合函数作为条件,比如只保留用户数大于 100 的城市。
从执行顺序看,先 where 过滤,再 group by 分组,接着执行聚合函数,最后 having 筛选分组。把本可放在 where 的条件错误地写到 having 里,会让数据库先分组再剔除,浪费大量资源。
select city, count(*) as user_count from user_info where status = 1 group by city having count(*) > 100;
3.1 聚合函数嵌套与表达式
MySQL 允许在 select 或 having 中使用聚合表达式,例如 sum(price * qty) 计算总金额,或 avg(age) 配合 round 保留小数。但标准 SQL 不允许直接嵌套两个聚合函数,如 avg(count(*)) 是非法的,需要借助子查询实现。
在复杂报表中,常把聚合结果作为子查询,再对外层做二次聚合。这样逻辑清晰,也便于调试。下面示例先统计每城市人数,再求城市平均人数。
select avg(user_count) as avg_city_size from ( select city, count(*) as user_count from user_info group by city ) as t;
四、常见误区与性能建议
一个典型误区是认为聚合函数会自动去重,实际上只有 count(distinct) 或配合 distinct 关键字才会去重。另一个误区是在 select 中混入非分组列却依赖运行不出错,这在迁移到严格模式或其他数据库时会暴露问题。
性能方面,为分组和过滤列建立索引十分关键。如果业务允许,尽量使用覆盖索引避免回表。对于超大型表,可考虑预先汇总到统计表,用定时任务更新,而不是每次实时聚合,从而保障查询体验。
| 函数 | 是否忽略 null | 典型用途 |
|---|---|---|
| count | 列模式忽略 | 统计行数或有效值 |
| sum | 忽略 | 数值累计 |
| avg | 忽略 | 平均值计算 |
| max/min | 忽略 | 极值查找 |
4.1 使用聚合函数处理空结果
当查询没有匹配任何行时,聚合函数一般返回 null,而不是 0。如果应用层未做判空,可能引发计算异常。可以使用 coalesce 包裹聚合结果,确保输出友好默认值。
如下示例在无人数据时返回 0,前端展示更平稳。这种写法在仪表盘和接口开发中非常推荐,能减少不必要的空指针判断。
select coalesce(sum(amount), 0) as total_amount from orders where create_date = '2023-01-01';