窗口函数对NULL的处理分两类:聚合函数(如SUM、AVG、COUNT)自动忽略NULL值,COUNT(*)统计所有行而COUNT(col)仅统计非NULL行;排序函数(如ROW_NUMBER、RANK)依赖ORDER BY中NULLS FIRST/LAST显式设定,否则按数据库默认规则排序(PostgreSQL/Oracle默认NULLS FIRST,MySQL 8.0+默认ASC时最后、DESC时最前),需用COALESCE或CASE主动干预以避免逻辑错误。

窗口函数里遇到 NULL 会怎么算?
窗口函数默认把 NULL 当作“不存在”,不参与聚合计算,但也不会报错——这容易让人误以为结果是“跳过并继续”,其实很多函数(比如 SUM()、AVG())直接忽略 NULL 值,而 ROW_NUMBER() 这类排序函数则照常编号,NULL 可能排在最前或最后,取决于 ORDER BY 的 NULLS FIRST 或 NULLS LAST 设置(PostgreSQL/Oracle 支持,MySQL 8.0+ 仅支持 NULLS FIRST)。
常见错误现象:AVG(salary) 返回值比预期小,不是因为平均值低,而是因为某几行 salary 是 NULL,被静默排除,分母变小;又或者用 LAG() 拿上一行值,结果拿到的是 NULL,后续计算全崩。
- 所有标准聚合类窗口函数(
SUM、AVG、COUNT、MIN、MAX)天然跳过NULL -
COUNT(*)统计行数,COUNT(col)只统计col非NULL的行 - 排序类函数(
RANK()、DENSE_RANK()、ROW_NUMBER())对NULL的排序行为依赖数据库实现和显式声明
COALESCE 和 IS NULL 怎么配合窗口函数用?
不能靠“等它自己处理”,得主动干预。核心思路是:在进窗口函数之前,把 NULL 显式转成有意义的值,或单独标记出来。
例如想让 AVG() 按“缺失即为 0”来算,就得写 AVG(COALESCE(salary, 0));如果想保留原始 NULL 含义(比如“未知薪资”不该参与均值),那就该用 AVG(salary),但必须意识到分母不含这些行。
-
COALESCE(salary, 0)最常用,但注意:0 可能扭曲业务语义(比如薪资为 0 和未知是两回事) -
CASE WHEN salary IS NULL THEN ... ELSE ... END更安全,可区分处理逻辑 - 避免在
ORDER BY子句中裸写salary,应改为salary DESC NULLS LAST(PostgreSQL/Oracle)或用COALESCE(salary, -1)模拟(MySQL 8.0)
MySQL 8.0 对 NULL 排序的支持很有限
MySQL 8.0 不支持 NULLS FIRST/LAST 语法,ORDER BY col DESC 默认把 NULL 排最前,ASC 则排最后——这个行为和 PostgreSQL 相反,且无法改写。如果业务要求 NULL 总在末尾,只能靠 COALESCE 或 IFNULL 临时垫值:
ORDER BY COALESCE(updated_at, '1970-01-01') DESC
但要注意:垫的值不能干扰真实数据范围(比如用 '9999-12-31' 垫时间字段,再 DESC 就真排第一了)。
-
IFNULL(col, 0)在数值场景够用,字符串建议用COALESCE(col, '') - 窗口函数内部的
ORDER BY不能用子查询或变量,垫值必须是确定性表达式 - 用
COALESCE垫值后,RANK()会把所有垫值行视为相同值,可能产生意外并列排名
用 FIRST_VALUE / LAST_VALUE 时 NULL 更危险
这两个函数默认只看当前窗口帧(ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),如果首行/末行恰好是 NULL,返回的就是 NULL,而且很难从结果里分辨是“真 NULL”还是“没取到值”。
更糟的是,LAST_VALUE 在未显式指定 ROWS 或 RANGE 时,实际行为依赖数据库——PostgreSQL 默认是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,MySQL 8.0 默认是 ROWS,结果可能完全不同。
- 强制指定完整帧:例如
LAST_VALUE(x) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 加过滤:用
FIRST_VALUE(COALESCE(x, 'N/A'))至少让空值可见 - 别依赖默认帧,尤其跨数据库迁移时,
LAST_VALUE行为差异最大
真正麻烦的不是 NULL 本身,而是它混在窗口帧里,既不报错也不提示,只悄悄改变聚合基数或排序位置。越复杂的窗口定义(多级 PARTITION BY + 多重 ORDER BY),越容易漏掉某个字段的 NULL 影响。

















