COUNT()统计每组总行数(含NULL),COUNT(列名)仅统计该列非NULL值;需明细行时用COUNT() OVER(PARTITION BY ...)窗口函数。

用 COUNT(*) 统计每组有多少行
GROUP BY 后要得到“每组几条数据”,最直接的方式就是 COUNT(*)。它统计的是分组后每个组的行数,包括 NULL 值所在的行——这点和 COUNT(列名) 不同。
常见错误是写成 COUNT(id) 却没意识到 id 有 NULL,结果漏计;或者误以为 COUNT(*) 会跳过空行(其实不会)。
-
SELECT dept, COUNT(*) FROM employees GROUP BY dept;—— 正确,统计每个部门人数 -
SELECT dept, COUNT(id) FROM employees GROUP BY dept;—— 若id允许为 NULL,该组数量会少于实际行数 - 不能在 SELECT 中混用非聚合字段和聚合函数而不加 GROUP BY,否则报错:
ERROR: column "name" must appear in the GROUP BY clause
统计组内非 NULL 值的数量用 COUNT(列名)
当你关心的是“某列有效值有多少个”,比如统计每个部门里有邮箱的员工数,就得用 COUNT(email)。它自动忽略该列值为 NULL 的行。
注意:不是所有数据库对 NULL 的处理完全一致(例如某些旧版 SQLite 行为略有差异),但主流 PostgreSQL、MySQL、SQL Server、Oracle 都遵循这一规则。
-
SELECT dept, COUNT(email) FROM employees GROUP BY dept;—— 只算 email 不为 NULL 的人 -
SELECT dept, COUNT(*) - COUNT(email) FROM employees GROUP BY dept;—— 可间接得出每个部门缺失 email 的人数 - 别写
COUNT(DISTINCT email)来代替,除非你真想排重——它统计的是不同邮箱个数,不是“有邮箱的人数”
想同时看总数和各组数量?用窗口函数 COUNT() OVER()
如果不想破坏原始行结构,又需要知道“当前行所属组一共有多少人”,就得用窗口函数 COUNT(*) OVER(PARTITION BY dept)。它不压缩行数,适合做占比、筛选 Top N 组等场景。
容易混淆的是把它和普通聚合搞混:加了 OVER 就不再是聚合查询,不需要 GROUP BY,也不能和 GROUP BY 混用在同一层 SELECT 中(会报错或语义异常)。
-
SELECT name, dept, COUNT(*) OVER(PARTITION BY dept) AS dept_size FROM employees;—— 每行都带所在部门总人数 -
SELECT dept, COUNT(*) FROM employees GROUP BY dept HAVING COUNT(*) > 5;—— 这是过滤分组,不是窗口函数 - PostgreSQL 和 MySQL 8.0+ 支持;SQLite 3.25+、SQL Server 2005+ 也支持,但旧版本不认
OVER
COUNT(1) 和 COUNT(*) 有区别吗?
没有实质区别。几乎所有主流数据库都会把 COUNT(1) 优化成 COUNT(*),两者都统计行数,都不关心具体值,也都包含 NULL 行。
写 COUNT(1) 是一些老开发的习惯(源于早期 Oracle 文档建议),但现在纯属风格问题。反而可能让新人误以为“1 是某列名”或“比 * 快一点”——实际上执行计划几乎一样,可读性还略差。
- 推荐统一用
COUNT(*),语义清晰,无歧义 - 别为了“看起来快”而写
COUNT(pk),主键虽非空,但多一次列查找,反而可能略慢 - 如果表有聚簇索引且只查行数,
COUNT(*)甚至可能走元数据(如 MySQL 的 InnoDB 在某些条件下),COUNT(列)则必须扫描该列
实际写的时候,先想清楚你要的是“这组总共几行”,还是“这组里某字段有几个非空值”,再决定用 COUNT(*) 还是 COUNT(列名);需要保留明细行时,别硬套 GROUP BY,直接上窗口函数。细节上,NULL 处理和数据库版本兼容性是最常翻车的地方。

















