PostgreSQL/SQLite中应优先使用FILTER子句而非CASE实现空值过滤,因其语义更清晰、性能略优且执行意图明确;FILTER仅支持AVG(salary) FILTER (WHERE condition)语法,不可嵌套或与窗口函数混用,且需显式处理NULL。

PostgreSQL/SQLite 中用 FILTER 而不是 CASE 实现空值过滤
如果你用的是 PostgreSQL 或较新版本的 SQLite(3.30+),FILTER 子句是比 CASE WHEN 更简洁、语义更清晰的空值过滤方式。它直接在聚合函数内部声明“只对满足条件的行生效”,不改变原始行数,也不引入额外的 NULL 分支逻辑。
常见错误现象:写 AVG(CASE WHEN salary > 10000 THEN salary END) 看似可行,但其实把不满足条件的行转成了 NULL,再交由 AVG 忽略——这属于“被动丢弃”,而 FILTER 是“主动限定输入集”,执行意图更明确,且部分场景下性能略优(优化器更容易推导统计范围)。
-
FILTER只支持 PostgreSQL 和 SQLite(MySQL、SQL Server、Oracle 均不支持) - 语法必须紧贴聚合函数后,用
WHERE引导条件,不能省略括号:AVG(salary) FILTER (WHERE salary > 10000) - 不能嵌套使用
FILTER,也不能和窗口函数混用(如AVG() OVER () FILTER (...)是非法的) - 若条件中涉及列可能为
NULL,需显式排除,例如WHERE salary IS NOT NULL AND salary > 10000,否则NULL会导致整行被滤掉(这是预期行为,不是 bug)
示例:按部门统计「月薪超 10000 的员工平均工资」和「所有员工平均工资」两列:
SELECT dept_name, AVG(salary) FILTER (WHERE salary > 10000) AS avg_high_salary, AVG(salary) AS avg_all_salary FROM department d LEFT JOIN employee e ON e.dept_id = d.dept_id GROUP BY dept_name;
MySQL / SQL Server / Oracle 中替代 FILTER 的等效写法
这些数据库不支持 FILTER,但可以用 CASE WHEN ... THEN value END 模拟——注意必须写成 THEN value 形式,不能写 THEN value ELSE NULL(因为 ELSE NULL 是冗余的,默认就是 NULL)。
关键区别在于:聚合函数对 CASE 表达式返回的 NULL 会自动忽略,所以效果等同于“仅用非 NULL 分支参与计算”。但容易踩的坑是误加 ELSE 0,这会让本该被过滤的行强行带入计算,彻底扭曲结果。
- 正确写法:
AVG(CASE WHEN salary > 10000 THEN salary END) - 错误写法:
AVG(CASE WHEN salary > 10000 THEN salary ELSE 0 END)(把低薪员工全算成 0,拉低均值) - Oracle 用户可选
NVL,但NVL不能用于过滤,只能替换值;真要过滤,仍得靠CASE - SQL Server 中
IIF不推荐用于此场景,因它不支持返回 NULL 的分支省略,必须写全ELSE,易出错
空值本身参与聚合时的默认行为必须清楚
除 COUNT(*) 外,所有标准聚合函数(AVG、SUM、MAX、MIN)默认忽略 NULL。这不是配置项,是 SQL 标准行为,无法关闭或反转。
这意味着:如果一列全是 NULL,AVG(col) 返回 NULL,而不是 0 或报错;COUNT(col) 返回 0;SUM(col) 也返回 NULL。这个 NULL 结果常被前端或报表工具误判为“无数据”,实际是“有数据但全空”。
- 需要返回 0 而非
NULL?用COALESCE(AVG(col), 0),不是AVG(COALESCE(col, 0))(后者会把空值当 0 算进分母) -
COUNT(*)统计行数,COUNT(col)统计col非空行数,二者语义完全不同,别混用 - 聚合前用
WHERE col IS NOT NULL是全局过滤,会影响整个分组;而CASE或FILTER是单字段局部过滤,同一行其他字段照常参与分组
复杂过滤逻辑下 FILTER 和 CASE 的取舍
当过滤条件涉及多列判断、子查询或动态阈值时,FILTER 就力不从心了——它只接受布尔表达式,不支持子查询或标量函数调用。这时必须退回 CASE WHEN,甚至配合窗口函数预计算。
例如:“统计每个部门中,薪资高于本部门平均薪资的员工平均工资”,FILTER 无法直接引用窗口计算出的部门均值,而 CASE 可以结合子查询或 CTE 实现:
SELECT dept_name, AVG(CASE WHEN e.salary > dept_avg.avg_salary THEN e.salary END) FROM employee e JOIN ( SELECT dept_id, AVG(salary) AS avg_salary FROM employee GROUP BY dept_id ) dept_avg ON e.dept_id = dept_avg.dept_id GROUP BY dept_name;
这种场景下,硬套 FILTER 会逼你写两遍聚合逻辑,反而增加维护成本。真正的技巧不在语法炫技,而在判断哪一层该过滤、哪一层该补值、哪一层该保留原始空状态——多数人卡住的地方,其实是没想清业务定义里的“空”到底代表“缺失”“无效”还是“零值”。











