SQL中NULL在GROUP BY中被视为相同值而归为一组,但业务上“未填”与“明确为空”需用CASE WHEN区分;ORDER BY NULLS LAST兼容性差,MySQL 5.7需用IF模拟;聚合函数自动忽略NULL,AVG全NULL时返回NULL而非0;WHERE中必须用IS NULL而非= NULL。

GROUP BY 遇到 NULL 时分组结果不一致?
SQL 标准里 NULL 在 GROUP BY 中被视为“相同值”,所以所有 NULL 会归入同一组——这点很多人误以为会跳过或报错,其实不会。但问题常出在:你查 SELECT col, COUNT(*) 时发现 col IS NULL 的行被合并了,而你原本想区分“没填”和“明确为空”的语义(比如业务上 region = NULL 是未分配,region = 'N/A' 是无效值),这时单纯靠 GROUP BY 无法拆开。
- 真正影响分组行为的是字段实际值,不是是否可空;只要值都是
NULL,就进同一组 - 若需把
NULL当独立类别处理(比如统计“空值占比”),用CASE WHEN col IS NULL THEN 'NULL' ELSE col END包一层再GROUP BY - PostgreSQL 和 SQL Server 支持
GROUPING SETS,能显式把NULL组和其他组合并展示,但 MySQL 8.0+ 才支持,老版本得靠UNION ALL拼接
ORDER BY ... NULLS LAST 在不同数据库表现不一?
NULLS LAST 是 SQL:2003 标准语法,但兼容性差:PostgreSQL、Oracle、SQL Server(2012+)原生支持;MySQL 直到 8.0.22 才支持,5.7 及更早版本直接报错 ERROR 1064;SQLite 完全不认这关键字。
- MySQL 5.7 或更低版本必须用
ORDER BY IF(col IS NULL, 1, 0), col模拟NULLS LAST - PostgreSQL 中
NULLS FIRST是默认行为,加NULLS LAST才翻转;而 Oracle 默认是NULLS LAST,加不加效果一样 - 如果排序字段是字符串且含
NULL,又用了COLLATE,注意某些 collation 会让NULL排在最前,即使写了NULLS LAST也可能失效(如 PostgreSQL 的en_US.utf8下正常,但自定义 collation 可能绕过规则)
聚合函数对 NULL 值的默认过滤容易误判结果
COUNT(*) 计所有行,但 COUNT(col)、SUM(col)、AVG(col) 全部自动忽略 NULL —— 这不是 bug,是标准行为。麻烦在于:当整组数据全是 NULL,AVG(col) 返回 NULL 而不是 0,前端可能渲染成空白,你以为漏数了,其实是没值。
-
COUNT(col)和COUNT(*)差异必须时刻绷着:前者只算非空,后者算全部 - 需要把空组的聚合结果补为
0,别用COALESCE(AVG(col), 0)简单包裹——它只改最终结果,不改变分母(即行数)。正确做法是COALESCE(SUM(col), 0) / NULLIF(COUNT(col), 0),避免除零 - 窗口函数如
AVG() OVER (PARTITION BY x)同样跳过NULL,但分区边界若全是NULL,该窗口结果就是NULL,没法靠DEFAULT关键字兜底
WHERE 条件里 IS NULL 和 = NULL 混用导致查不到数据
这是新手掉进最多次的坑:WHERE col = NULL 永远不成立,因为 NULL = NULL 返回 UNKNOWN,不是 TRUE,所以整行被过滤掉。必须写 WHERE col IS NULL。
- ORM 如 SQLAlchemy 或 Django ORM 生成的查询一般会自动转成
IS NULL,但手写 SQL 或拼接动态条件时极易写错 - 如果字段有索引,
IS NULL在多数数据库(PostgreSQL、MySQL 8.0+)能走索引;但col IS NOT NULL在某些旧版本 MySQL 上可能无法利用索引,需配合函数索引或覆盖索引优化 - 联合索引中,若第一个字段允许
NULL,且查询条件是WHERE first_col IS NULL AND second_col = 'x',部分数据库(如 MySQL)可能无法高效使用该联合索引,得看执行计划里的key_len是否完整命中
NULL 的语义模糊性决定了它永远不是“一个值”,而是“缺失标记”。所有涉及它的操作——分组、排序、聚合、条件过滤——都得按标准重新校验一遍,不能凭直觉。最危险的是跨数据库迁移时,一条在 PostgreSQL 跑得好的 ORDER BY x NULLS LAST,换到 MySQL 5.7 就直接挂掉,连错误提示都不够明确。

















