GROUP BY + COUNT 用法拓展

一、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 (主键) 补充性能细节

  1. COUNT(主键id)主键 NOT NULL,结果和COUNT(*)完全一致;InnoDB 不会更快,不推荐。
  2. 大表无索引场景COUNT(1)COUNT(*)性能几乎无差别;MySQL 优化器自动优化。
  3. 统计带过滤总行数

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;

九、易踩坑补充总结

  1. COUNT(字段) 忽略 NULL,不忽略空字符串''
  2. COUNT(DISTINCT col) 自动过滤 NULL,NULL 不参与去重;
  3. GROUP BY 后 WHERE 不能写聚合函数,必须放 HAVING;
  4. 多字段同时非 NULL 计数,MySQL 不能用 COUNT (col1,col2),要用 CASE;
  5. 窗口 COUNT 不会合并分组,GROUP BY COUNT 会压缩分组;
  6. 条件计数优先 COUNT(CASE...),比子查询、SUM (IF) 可读性更强。