MySQL COUNT(DISTINCT) 忽略 NULL 时如何统计缺失值
报表要统计“有多少个不同客户”时,COUNT(DISTINCT customer_id) 返回的只是非 NULL 客户数量。它不会把缺失值当成一个客户,也不会把每一行缺失记录都算进去。若业务要把“未填写客户”作为一个独立类别,需要在查询中明确补回一个 NULL 桶。
官方地址:https://dev.mysql.com/doc/refman/8.4/en/
COUNT(DISTINCT customer_id)统计非 NULL 的唯一客户。SUM(customer_id IS NULL)统计缺失记录行数,不是缺失类别数。- 要把 NULL 当成一个类别,可用“非空去重数 + 是否存在 NULL”,或统计
DISTINCT子查询。
先把三个“数”分开:非空去重、缺失行、缺失桶
假设订单表是 orders,其中 customer_id 允许为空。下面这组数据里,客户 101 出现两次,客户 102 出现一次,还有两条订单没有客户编号:
-- 建立一组最小示例,NULL 表示订单没有关联客户
CREATE TEMPORARY TABLE orders (
order_id INT PRIMARY KEY,
store_id INT NOT NULL,
customer_id INT NULL
);
-- 101 重复出现,NULL 出现两次,便于区分三种统计口径
INSERT INTO orders (order_id, store_id, customer_id) VALUES
(1, 10, 101), (2, 10, 101), (3, 10, NULL),
(4, 10, 102), (5, 10, NULL);
此时可以把需求拆成三问:已知客户有几个、缺失订单有几行、把“缺失客户”作为一个类别后共有几类。
| 业务口径 | 写法 | 含义 |
|---|---|---|
| 已知唯一客户数 | COUNT(DISTINCT customer_id) | 忽略 NULL,结果为 2 |
| 缺失订单行数 | SUM(customer_id IS NULL) | 每条 NULL 都计数,结果为 2 |
| 唯一客户类别数 | 已知唯一客户数 + NULL 是否存在 | NULL 只算一个桶,结果为 3 |

用缺失桶公式补回一个 NULL
如果报表要回答“每个门店覆盖了多少种客户状态”,可以把是否存在 NULL 转成 0 或 1,再加到非空去重数上:
-- COUNT(*) 统计行数,COUNT(customer_id) 只统计非 NULL 客户
SELECT
COUNT(DISTINCT customer_id)
+ (COUNT(*) > COUNT(customer_id)) AS customer_bucket_count
FROM orders;
-- 101、102 加上一个 NULL 桶,结果为 3
这里的布尔表达式在 MySQL 中会转成 1 或 0:两者不相等,说明至少有一行 customer_id 为 NULL。空结果集的两个 COUNT 都是 0,表达式不会凭空创建 NULL 桶,这正是通常想要的边界。
不要直接把 NULL 替换成一个看似不会出现的数字或字符串,除非这个哨兵值已经被约束保证永不出现在真实数据里。否则真实客户编号恰好等于哨兵值时,去重结果会少算一个类别;字符串列还可能触发隐式类型转换。
分组统计时,让每个分组独立判断 NULL
把查询按 store_id 分组后,缺失判断必须和聚合一起发生在当前分组内:
-- 每个门店分别计算已知客户、缺失行和 NULL 桶
SELECT
store_id,
COUNT(DISTINCT customer_id) AS known_customer_count,
SUM(customer_id IS NULL) AS missing_order_count,
COUNT(DISTINCT customer_id)
+ (COUNT(*) > COUNT(customer_id)) AS customer_bucket_count
FROM orders
GROUP BY store_id;
-- 不同门店的 NULL 不会被合并,缺失桶只在本门店存在时加 1
COUNT(*) 和 COUNT(customer_id) 都受到同一个 WHERE 条件影响,所以筛选日期、订单状态或门店后再做比较,口径仍然一致。若把全表的 NULL 判断放到分组查询外,就可能给没有缺失值的门店也加上一个桶。

需要稳定通用写法时使用 DISTINCT 子查询
MySQL 的 DISTINCT 会把多个 NULL 视为同一个去重值,因此可以先取唯一值,再统计子查询行数:
-- 子查询保留非 NULL 唯一客户,同时保留一个 NULL 行
SELECT COUNT(*) AS customer_bucket_count
FROM (
SELECT DISTINCT customer_id
FROM orders
) AS unique_customer_values;
-- 子查询的 NULL 只保留一行,结果仍为 3
这种写法更贴近“去重后的值集合有多少行”,适合需要继续查看唯一值集合、或不想维护哨兵值约定的场景。若还要按门店统计,可把 store_id 一并放进子查询的选择列表,再按业务定义决定是统计“门店-客户组合”还是每个门店内部的客户类别。
上线前用口径清单检查查询
- 问题问的是“已知客户数”还是“客户状态类别数”?前者用
COUNT(DISTINCT customer_id),后者才补 NULL 桶。 - 问题问的是缺失记录量,使用
SUM(customer_id IS NULL),不要把它和唯一值数量混用。 - 确认
NULL与空字符串、0、未知客户编号是不同业务状态;COALESCE不会自动替你定义业务含义。 - 分组、过滤和连接后再聚合,确保三个 COUNT 表达式看到的是同一批行。
相关问题
COUNT(column) 和 COUNT(*) 有什么区别?
COUNT(*) 统计筛选后的行数;COUNT(column) 只统计该列非 NULL 的行。判断一组数据是否出现 NULL 时,常比较两者。
NULL 和空字符串会被 COUNT(DISTINCT) 一起忽略吗?
不会。NULL 是缺失值,空字符串是一个实际字符串值;对字符列来说,空字符串可以作为一个非 NULL 的唯一值参与统计。
为什么不总是用 COALESCE(customer_id, -1)?
因为 -1 可能是合法值,且不同列类型需要不同哨兵值。若没有严格约束,优先用“存在 NULL 加一”或 DISTINCT 子查询表达真实口径。
Go strings.Cut 和 Split 处理键值文本有什么区别
- 上一篇
- Go strings.Cut 和 Split 处理键值文本有什么区别
- 下一篇
- Go net.Dialer Timeout 和 Deadline 该如何分别设置
-
- 数据库 · MySQL | 22分钟前 |
- MySQL GROUP_CONCAT 如何按排序规则拼接稳定结果
- 488浏览 收藏
-
- 数据库 · MySQL | 2小时前 | SQL查询 · group by · MySQL教程 · mysql group by ONLY_FULL_GROUP_BY 函数依赖 ERROR 1055
- MySQL ONLY_FULL_GROUP_BY 遇到函数依赖时如何改写查询
- 440浏览 收藏
-
- 数据库 · MySQL | 22小时前 | MySQL · DDL · InnoDB · mysql innodb ALGORITHM=INSTANT Instant DDL
- Instant DDL 表结构限制怎么配置或排查
- 486浏览 收藏
-
- 数据库 · MySQL | 1天前 | MySQL · 字符集 · REGEXP_LIKE · Collation · mysql 中文 排序规则 utf8mb4 REGEXP_LIKE
- REGEXP_LIKE 中文排序规则怎么配置或排查
- 169浏览 收藏
-
- 数据库 · MySQL | 1天前 | MySQL · JSON_TABLE · 嵌套数组 · JSON_TABLE MySQL JSON
- JSON_TABLE 嵌套数组怎么配置或排查
- 181浏览 收藏
-
- 数据库 · MySQL | 1天前 | MySQL · CTE · SQL排错 · mysql cte_max_recursion_depth 递归 CTE
- 递归 CTE 终止条件怎么配置或排查
- 338浏览 收藏
-
- 数据库 · MySQL | 1天前 | MySQL · 索引 · Invisible Index ·
- Invisible Index 灰度验证怎么配置或排查
- 156浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 11次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 125次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 49次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 17次使用
-
- OpenCompass
- OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
- 68次使用
-
- MySQL 明明加了索引,为什么查询还是很慢?先查这 6 个点
- 2026-06-27 374浏览
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- golang 基于 mysql 简单实现分布式读写锁
- 2023-01-07 384浏览
-
- 详解如何利用GORM实现MySQL事务
- 2023-01-07 184浏览
-
- Go语言实现操作MySQL的基础知识总结
- 2023-01-23 265浏览

