PostgreSQL和Oracle默认NULLS FIRST,SQL Server(2022+)支持但旧版不支持且ASC时NULL排最前、DESC时排最后,MySQL 8.0+默认NULLS LAST;必须显式声明NULLS FIRST/LAST或用CASE模拟以保证跨库一致性。

ORDER BY 中 NULL 值默认排在哪?
SQL 窗口函数(如 ROW_NUMBER()、RANK()、LEAD())的排序行为完全继承自 ORDER BY 子句,而不同数据库对 NULL 的默认排序位置不一致:PostgreSQL 和 SQL Server 默认把 NULL 排在末尾,MySQL 8.0+ 默认排在开头,Oracle 则取决于 NLS_SORT 设置。不显式声明,结果就不可控。
- 显式用
NULLS FIRST或NULLS LAST控制位置(标准 SQL,PostgreSQL/Oracle/SQL Server 2022+ 支持) - MySQL 目前仍不支持这两个关键字,得用
IS NULL表达式模拟:ORDER BY col IS NULL, col
这会让NULL排最前;反过来写ORDER BY col IS NOT NULL, col就排最后
使用 RANK() / DENSE_RANK() 时 NULL 值会“占位”吗?
会。只要参与排序的列含 NULL,窗口函数就会把它当作一个有效排序值处理——NULL = NULL 在排序中成立(即所有 NULL 被视为相等),所以它们会挤在一块并共享同一排名。
-
RANK()遇到三个NULL,会给出1, 1, 1, 4, ...(跳过后续序号) -
DENSE_RANK()同样是1, 1, 1, 2, ... - 若想让
NULL不参与排名(比如只给非空值编号),必须过滤或隔离:COALESCE(col, 'ZZZZZ') -- 字符串场景临时兜底
CASE WHEN col IS NULL THEN -1 ELSE col END -- 数值场景重映射
注意:这种技巧会改变原始值语义,仅适用于纯排序用途,不能用于后续计算依赖原值的逻辑
LAG() / LEAD() 在 NULL 区域返回什么?
LAG() 和 LEAD() 本身不关心值是否为 NULL,只按物理顺序取上一行或下一行的值。但如果排序后大量 NULL 挤在一起,就会出现连续多行都从同一个 NULL 行拉取值的情况——这不是 bug,是排序 + 窗口帧共同作用的结果。
- 典型陷阱:用
LEAD(col) OVER (ORDER BY col)想找“下一个非空值”,但实际返回的是下一个物理行的col,哪怕它也是NULL - 正确做法是先用
ROW_NUMBER() OVER (PARTITION BY CASE WHEN col IS NOT NULL THEN 1 ELSE 0 END ORDER BY ...)分组编号,再做关联查找;或者改用递归 CTE / 外部 JOIN 模拟“下一个非空”
性能隐患:ORDER BY NULLS LAST 加索引有用吗?
有,但有限。大多数数据库(PostgreSQL、Oracle)支持在索引中声明 NULLS LAST,例如:
CREATE INDEX idx_col_nulls_last ON tbl (col NULLS LAST);这样能加速
ORDER BY col NULLS LAST 的窗口排序。但注意:
- MySQL 无法在索引定义中指定
NULLS行为,只能靠col IS NOT NULL条件配合覆盖索引缓解 - 如果排序字段本身
NULL占比超过 60%,B-tree 索引效率会明显下降,此时应考虑是否真需要对全量数据排序,或提前用WHERE col IS NOT NULL过滤 - 窗口函数的排序开销是 O(n log n),当
n达到百万级且NULL分布稀疏时,实际耗时可能比预期高 3–5 倍——别只看执行计划里的“Index Scan”,要盯住Sort节点的实际内存/磁盘使用
真实场景里,最常被忽略的是:你写的 NULLS LAST 在开发环境生效,上线后因为数据库版本或方言差异(比如用了旧版 SQL Server),悄悄退化成默认行为,导致分页错位或排名重复。上线前务必在目标环境中验证排序输出的首尾几行。

















