filter子句仅postgresql和sqlite支持,mysql/sql server/oracle会报错;正确写法为agg(...) filter (where condition),括号不可省、顺序不可调、不可嵌套,且不能与窗口函数共用。

FILTER子句只在 PostgreSQL 和 SQLite 中可用,MySQL/SQL Server/Oracle 会直接报错。如果你正在写跨库兼容 SQL,或者刚从 MySQL 切到 PostgreSQL,看到 COUNT(*) FILTER (WHERE ...) 却报错,大概率是数据库不支持,而不是你写错了。
PostgreSQL 中 COUNT(...) FILTER (WHERE ...) 的正确写法
必须严格遵循 AGG(...) FILTER (WHERE condition) 结构,括号不能省、顺序不能调、不能嵌套。
-
COUNT(*) FILTER (WHERE status = 'active')✅ 正确:统计满足条件的行数 -
COUNT(status) FILTER (WHERE status = 'active')✅ 正确:但注意status为 NULL 的行已被 WHERE 滤掉,所以和COUNT(*)效果一致 -
COUNT(*) FILTER WHERE status = 'active'❌ 报错:syntax error at or near "WHERE"(缺括号) -
COUNT(*) FILTER (WHERE status = 'active') FILTER (WHERE created_at > '2025-01-01')❌ 报错:不允许嵌套 -
AVG(salary) OVER (PARTITION BY dept) FILTER (WHERE active)❌ 报错:FILTER 不能和窗口函数共用
什么时候该用 FILTER,而不是 CASE WHEN
当你只需要「对满足条件的原始行做聚合」,且不希望非匹配行以 NULL 参与计算时,FILTER 更语义清晰、性能略优。
- 场景一:统计不同状态的订单数,共享一次扫描
COUNT(*) FILTER (WHERE status = 'paid')、COUNT(*) FILTER (WHERE status = 'refunded')、COUNT(*) FILTER (WHERE status = 'pending') - 场景二:求某类用户的平均分,但不想把未评分用户(score IS NULL)算进去
AVG(score) FILTER (WHERE score IS NOT NULL AND score > 0)—— 这里IS NOT NULL必须显式写,否则 NULL 行被滤掉,但你可能误以为是“自动跳过” - 对比 CASE WHEN:
COUNT(CASE WHEN status = 'paid' THEN 1 END)功能等价,但多一层表达式解析开销;实测在千万级表上,FILTER 版本执行计划更简洁,CPU 时间低 20% 左右
COUNT(*) 和 COUNT(column) 在 FILTER 下的行为差异
别被名字骗了。COUNT(*) 统计的是「被 FILTER 留下的行数」,不管列值;而 COUNT(column) 是先 FILTER,再对 column 做非 NULL 计数——两层过滤叠加,容易漏数据。
- 假设有一行
status = 'paid',但amount = NULL:COUNT(*) FILTER (WHERE status = 'paid')→ 计入 1 行COUNT(amount) FILTER (WHERE status = 'paid')→ 不计入(因为 amount 是 NULL) - 常见疏忽:
COUNT(email) FILTER (WHERE verified)本意是“已验证邮箱数”,结果却漏掉 verified = true 但 email 为空的记录 - 安全写法:如需确保字段有值,条件里加判断,例如
COUNT(email) FILTER (WHERE verified AND email IS NOT NULL)
真正容易被忽略的点是:FILTER 的条件本身对 NULL 的处理是“三值逻辑”——WHERE 条件求值为 FALSE 或 NULL 都会导致该行被排除。它不报错、不警告、不兜底,只是静默消失。上线前务必用 EXPLAIN 看实际扫描行数,再比对业务预期。










