COUNT 是什么
COUNT 统计行数或非空值的个数:COUNT(*) 统计结果集行数,COUNT(col) 只统计该列非 NULL 的值,COUNT(DISTINCT col) 统计去重后的个数。它在没有匹配行时返回 0,而不是 NULL,这一点与 SUM、AVG 不同——后者在没有数据时返回 NULL。COUNT 忽略 NULL 的特性常被用来做条件计数,例如把布尔判断包进 COUNT,一次查询同时得到几个口径的计数。
基本信息
| COUNT(*) | 统计行数,包含全为 NULL 的行 |
|---|---|
| COUNT(col) | 统计该列非 NULL 的值的个数 |
| COUNT(DISTINCT col) | 去重后计数;MySQL 支持多列组合去重 |
| 无匹配行 | 返回 0,不是 NULL |
| 大表代价 | InnoDB 需要遍历索引统计,WHERE 为空时很慢 |
| 适用平台 | 标准 SQL 函数,MySQL 8.0 与 PostgreSQL 一致 |
典型使用场景
- 列表总数:分页组件需要显示总条数
- 条件计数:一次查询里同时统计有效、无效、待审核各多少条
- 去重计数:统计独立访客数 COUNT(DISTINCT user_id)
- 存在性判断:COUNT(*) > 0 可改写为 EXISTS 更省资源
- 数据质量:用 COUNT(*) 减 COUNT(col) 得到该列的缺失行数
- 分组统计:GROUP BY 之后计算每组的行数
多口径一次统计
多口径一次统计
SELECT COUNT(*) AS all_rows, # 总行数
COUNT(phone) AS has_phone, # 手机号非空的行
COUNT(DISTINCT user_id) AS user_kinds, # 去重用户数
COUNT(IF(status = 1, 1, NULL)) AS valid_rows, # 条件计数
COUNT(*) - COUNT(phone) AS missing_phone # 缺失量
FROM users;
# PostgreSQL 里条件计数写 SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END)大表计数的替代写法
大表计数的替代写法
-- 精确计数要遍历索引,大表上代价高
SELECT COUNT(*) FROM big_table WHERE created_at >= '2024-01-01';
-- 只需要判断有没有数据时用 EXISTS,命中即停
SELECT EXISTS (SELECT 1 FROM big_table WHERE user_id = 100);
-- PostgreSQL 可用统计信息估算,速度快但不精确,仅用于容量评估
SELECT reltuples::bigint FROM pg_class WHERE relname = 'big_table';COUNT 家族对照
| 写法 | 统计对象 | 无数据时 |
|---|---|---|
| COUNT(*) | 所有行 | 0 |
| COUNT(col) | col 非 NULL 的行 | 0 |
| COUNT(DISTINCT col) | col 去重后的非空值 | 0 |
| SUM(col) | 数值列合计,忽略 NULL | NULL |
| AVG(col) | 数值列平均,忽略 NULL | NULL |
| MIN / MAX(col) | 最小与最大值,忽略 NULL | NULL |
COUNT(*) 与 COUNT(1) 结果相同,MySQL 与 PostgreSQL 都会把它们视作同一类统计,选哪个只影响书写习惯。
大表上无条件 COUNT(*) 会长时间占用 IO,在线上主库执行前先确认表规模,必要时改用统计信息或异步汇总表。
COUNT(DISTINCT a, b) 统计的是组合去重个数;如果只想按一列去重而展示另一列,要先用子查询去重再计数。
COUNT 与聚合一起用时,WHERE 决定了参与统计的行:先过滤再计数,口径要写清楚,避免把 LEFT JOIN 补出的 NULL 行也算进去。
计数实践要点
- 总数与列表分开查:大表的精确统计交给覆盖索引或缓存,别让每个请求都重算
- 只需要判断有没有数据时用 EXISTS:命中一条即停止扫描,比 COUNT 便宜得多
- 条件计数一次搞定:多个判断包进 CASE 或 IF,避免同一张表扫好几遍
- 去重计数注意开销:COUNT(DISTINCT) 要维护去重结构,高基数列上代价明显
- 分组计数配合 HAVING:先按维度计数再筛异常组,是数据质量检查的常用组合
- LEFT JOIN 之后要指明统计对象:COUNT(*) 会把补出来的 NULL 行也算进去
PostgreSQL 里布尔表达式不能直接求和,条件计数统一写成 SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END),与 MySQL 的 IF 写法对应。
统计口径要写进文档:同一张表按行数统计还是按去重用户数统计,结果可能相差一个量级,报表需求确认时先对齐这一点。
计数口径与工具
- 总数口径先写清:是行数还是去重后的实体数,两者可能相差一个量级
- 分页总数可以用近似值:大数据量后台列表允许显示约数,避免每次都精确统计
- 增量统计优于实时统计:日活这类指标放汇总表按天增量更新
- COUNT(*) 与 SUM(1) 结果等价:都统计行数,团队内统一一种写法即可
- 统计结果要能复现:过滤条件与统计时间点记录下来,便于事后核对
统计前先想清楚要不要把 NULL 算进去:COUNT(*) 统计所有行,COUNT(col) 跳过 NULL,同一条 SQL 里把两者一起输出,口径差异一眼可见。