一、COUNT (DISTINCT 字段) 分组去重统计
统计分组内不重复、且非 NULL的值数量,NULL 直接剔除。
示例表 sales
| dept | user_id | goods |
|---|---|---|
| 销售 1 | 001 | 手机 |
| 销售 1 | 001 | 电脑 |
| 销售 1 | 002 | 手机 |
| 销售 2 | NULL | 耳机 |
sql
-- 每个部门有多少个独立员工(去重)
SELECT dept, COUNT(DISTINCT user_id) user_cnt
FROM sales
GROUP BY dept;
结果:销售 1 → 2(001、002)销售 2 → 0(user_id 为 NULL 不计)
扩展:多字段去重
COUNT(DISTINCT CONCAT(col1,',',col2))
sql
-- 统计分组内 员工+商品 组合不重复数量
SELECT dept, COUNT(DISTINCT CONCAT(user_id,',',goods))
FROM sales
GROUP BY dept;
二、条件计数(重点高频:COUNT + CASE WHEN)
需求:分组内满足某条件才计数,不满足不计,替代多段子查询,性能更好。语法:
sql
COUNT(CASE WHEN 条件 THEN 1 END)
原理:不满足条件返回 NULL,COUNT 自动忽略 NULL,只统计匹配行。
示例:按部门统计 手机订单、电脑订单数量
sql
SELECT
dept,
COUNT(*) total_order, -- 总订单
COUNT(CASE WHEN goods = '手机' THEN 1 END) phone_cnt,
COUNT(CASE WHEN goods = '电脑' THEN 1 END) pc_cnt
FROM sales
GROUP BY dept;
MySQL 简化写法:COUNT (IF ())
sql
SELECT
dept,
COUNT(IF(goods='手机',1,NULL)) phone_cnt
FROM sales
GROUP BY dept;
不等条件、多条件组合
sql
-- 统计价格大于1000、状态正常的订单
COUNT(CASE WHEN price > 1000 AND status = 1 THEN 1 END)
三、COUNT 统计 NULL / 空值反向统计
需求:统计分组内某字段为 NULL 的行数思路:匹配 NULL 时返回 1,其余返回 NULL
sql
SELECT
dept,
COUNT(CASE WHEN user_id IS NULL THEN 1 END) null_user_cnt
FROM sales
GROUP BY dept;
区分:
''空字符串 ≠ NULL,不会被上面语句统计
sql
-- 统计字段为空字符串的行
COUNT(CASE WHEN name = '' THEN 1 END)
四、COUNT 配合 HAVING 过滤分组聚合结果
WHERE 过滤原始行,HAVING 过滤 COUNT 聚合后的分组。
例:只展示订单总数≥2 的部门
sql
SELECT dept, COUNT(*) total
FROM sales
GROUP BY dept
HAVING COUNT(*) >= 2;
搭配条件计数过滤
sql
-- 筛选手机订单≥1的部门
SELECT dept, COUNT(CASE WHEN goods='手机' THEN 1 END) phone
FROM sales
GROUP BY dept
HAVING phone >= 1;
五、COUNT 多字段混用:COUNT (col1,col2) 仅 Oracle 支持
Oracle 特有:COUNT(expr1, expr2,...)规则:所有字段都不为 NULL 才计数,任意一个为 NULL 则不计
sql
-- Oracle专用
SELECT dept, COUNT(user_id, goods) valid_record
FROM sales
GROUP BY dept;
MySQL/PostgreSQL 不支持多参数 COUNT,需改用 CASE:
sql
-- 跨数据库通用替代方案
COUNT(CASE WHEN user_id IS NOT NULL AND goods IS NOT NULL THEN 1 END)
六、COUNT + 窗口函数(不压缩分组,保留明细)
普通 GROUP BY 会合并行;窗口 COUNT 可以每行带出分组统计值,不合并数据。
sql
SELECT
dept,
goods,
COUNT(*) OVER (PARTITION BY dept) dept_total
FROM sales;
效果:每一行都会显示当前部门总订单数,无需子查询关联。
窗口条件计数
sql
COUNT(CASE WHEN goods='手机' THEN 1 END) OVER(PARTITION BY dept)
七、COUNT (*) / COUNT (1) / COUNT (主键) 补充性能细节
COUNT(主键id)主键 NOT NULL,结果和COUNT(*)完全一致;InnoDB 不会更快,不推荐。- 大表无索引场景
COUNT(1)、COUNT(*)性能几乎无差别;MySQL 优化器自动优化。 - 统计带过滤总行数
sql
-- 错误:count(字段)会丢失NULL行
SELECT dept, COUNT(*) FROM t WHERE col IS NOT NULL GROUP BY dept;
八、COUNT 常见特殊场景用法
1. 统计分组内 满足条件的去重数量
sql
-- 每个部门购买手机的独立用户数
COUNT(DISTINCT CASE WHEN goods='手机' THEN user_id END)
2. 布尔值简化(PostgreSQL)
pg 中 true=1,false=0,可直接:
sql
COUNT((goods = '手机')::int)
3. 统计百分比(聚合嵌套)
sql
SELECT
dept,
COUNT(*) total,
ROUND(COUNT(CASE WHEN goods='手机' THEN 1 END) * 100.0 / COUNT(*),2) phone_ratio
FROM sales
GROUP BY dept;
九、易踩坑补充总结
COUNT(字段)忽略 NULL,不忽略空字符串'';COUNT(DISTINCT col)自动过滤 NULL,NULL 不参与去重;- GROUP BY 后 WHERE 不能写聚合函数,必须放 HAVING;
- 多字段同时非 NULL 计数,MySQL 不能用 COUNT (col1,col2),要用 CASE;
- 窗口 COUNT 不会合并分组,GROUP BY COUNT 会压缩分组;
- 条件计数优先
COUNT(CASE...),比子查询、SUM (IF) 可读性更强。