首页/数据/SQL 速查/COUNT 计数
聚合与窗口函数54 条

COUNT 计数

统计行数或非空值个数:COUNT(*) 统计行数,COUNT(col) 只统计该列非 NULL 的值;无匹配行时返回 0。

SQL 速查数据更新于 2026-10-13

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)数值列合计,忽略 NULLNULL
AVG(col)数值列平均,忽略 NULLNULL
MIN / MAX(col)最小与最大值,忽略 NULLNULL
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 里把两者一起输出,口径差异一眼可见。

易错点

  • 把 COUNT(col) 当成行数用,遇到含 NULL 的列会少算
  • 认为 COUNT 在无数据时返回 NULL,业务代码做了多余的判空分支
  • 在大表上做无条件 COUNT(*) 统计在线数据量,导致主库 IO 抖动
  • LEFT JOIN 之后 COUNT(*),未匹配行的 NULL 也被计入,总数比预期大
  • COUNT(DISTINCT col) 与 GROUP BY 混用造成重复计算,报表口径前后不一致

数据来源与参考

  • MySQL 8.0 Reference Manual · COUNT
  • PostgreSQL Documentation · Aggregate Functions
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌