当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL COUNT(DISTINCT) 忽略 NULL 时如何统计缺失值

MySQL COUNT(DISTINCT) 忽略 NULL 时如何统计缺失值

来源:17golang原创 2026-09-14 10:58:51 0浏览 收藏

报表要统计“有多少个不同客户”时,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
MySQL COUNT DISTINCT NULL 统计口径静态关系图,展示 orders 表、customer_id 与三个 COUNT 表达式及 NULL 缺失桶的关系
图1:MySQL 聚合口径的静态关系示意,重点看 customer_id 与三个 COUNT 表达式之间的差别。

用缺失桶公式补回一个 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 判断放到分组查询外,就可能给没有缺失值的门店也加上一个桶。

MySQL 按 store_id 分组统计 COUNT DISTINCT NULL 的查询结构图,展示组内聚合与缺失桶判断关系
图2:按门店分组统计的静态关系示意,缺失判断与去重结果都属于当前 store_id 分组。

需要稳定通用写法时使用 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 子查询表达真实口径。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go strings.Cut 和 Split 处理键值文本有什么区别Go strings.Cut 和 Split 处理键值文本有什么区别
上一篇
Go strings.Cut 和 Split 处理键值文本有什么区别
Go net.Dialer Timeout 和 Deadline 该如何分别设置
下一篇
Go net.Dialer Timeout 和 Deadline 该如何分别设置
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    11次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    125次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    49次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    17次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    68次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码