移动窗口中位数无法用PERCENTILE_CONT直接实现,因其不支持ROWS BETWEEN/RANGE BETWEEN;需用ROW_NUMBER()+COUNT()构造子集,或PostgreSQL中通过WITHIN GROUP在子查询中模拟。

移动窗口中位数不能直接用 PERCENTILE_CONT
PERCENTILE_CONT 不支持 ROWS BETWEEN 或 RANGE BETWEEN 这类移动窗口定义,它只接受 PARTITION BY + ORDER BY 的静态分组排序,无法表达“最近 7 行”或“过去 30 天”这种滑动范围。你写 PERCENTILE_CONT(0.5) OVER (ORDER BY ts ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) 会直接报错:函数不支持 ROWS 子句。
原因在于 SQL 标准里 PERCENTILE_CONT 是“分布型窗口函数”,设计目标是按完整分组排序后插值,不是为滚动计算优化的。PostgreSQL、SQL Server、BigQuery 都遵循这一限制。
- 想算移动中位数,必须换思路:先用
ROW_NUMBER()和COUNT() OVER构造每行对应的“窗口内子集”,再对这个子集调用聚合式PERCENTILE_CONT(仅 PostgreSQL/Oracle 支持)或手动取中间值 - SQL Server 和 BigQuery 不支持在子查询中嵌套
WITHIN GROUP调用,只能退回到ROW_NUMBER()+ 条件过滤的模拟方案 - 如果数据量大且窗口宽(如 1000 行),每次重排子集性能很差,建议提前物化时间序号或用临时表预处理
PostgreSQL 中可用 WITHIN GROUP 模拟移动中位数
PostgreSQL 允许在子查询中对动态生成的数组或横向展开的行集使用 PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ...),这是目前最接近“移动中位数”的可行路径。
示例:按时间戳计算每个点向前 6 行(含自身)的薪资中位数
SELECT
ts,
salary,
(SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY s.salary)
FROM (
SELECT salary
FROM employees e2
WHERE e2.ts = (
SELECT MIN(ts) FROM (
SELECT ts FROM employees e3
WHERE e3.ts
- 核心是外层每行触发一次子查询,构造出该行对应的时间窗口数据集
-
LIMIT 7确保最多取 7 行,配合MIN(ts)找到窗口左边界 - 注意:该写法在大数据量下极慢,
ts字段必须有索引,否则全表扫描 - 如果允许近似结果,可改用
APPROX_PERCENTILE_CONT(BigQuery)或扩展插件(如 PostgreSQL 的tdigest)
SQL Server / BigQuery 必须用 ROW_NUMBER() 手动模拟
这些数据库不支持子查询内调用 WITHIN GROUP,只能靠排序编号 + 奇偶判断来逼近中位数——本质是取窗口内第 FLOOR((n+1)/2) 和 CEIL((n+1)/2) 行的平均值。
关键步骤:
- 用
ROW_NUMBER() OVER (ORDER BY ts)给原始数据打全局序号 - 用自连接或 LATERAL(BigQuery)拉出每个点的窗口行,再对这批行重新
ROW_NUMBER() OVER (ORDER BY salary) - 筛选出新序号落在中间位置的行,最后用
AVG()合并两个值(偶数时)或单个值(奇数时) - BigQuery 可用
ARRAY_AGG(salary ORDER BY salary)[OFFSET(...)]简化取值,但数组大小受内存限制
典型陷阱:ORDER BY 在窗口内必须严格一致,若原始 ts 有重复,需加唯一键(如 id)避免排序不稳定导致中位数跳变。
NULL 值和排序方向必须显式控制
移动窗口场景下,NULL 出现在窗口内会极大干扰结果:它既可能被忽略(默认行为),也可能因 ORDER BY 排序策略不同(NULLS FIRST vs NULLS LAST)导致中间位置偏移。
- PostgreSQL 默认
NULLS LAST,SQL Server 视兼容级别而定,BigQuery 默认NULLS FIRST - 务必在所有
ORDER BY子句中显式声明:ORDER BY salary ASC NULLS LAST - 若业务要求把
NULL当作 0 处理,必须统一用COALESCE(salary, 0)替换,不能只在最终聚合里补 - 移动窗口中某次计算若全为
NULL,结果是NULL;但若只有一两个NULL,排序后它们会挤占位置,使真实数值的相对序号变化
真正难处理的不是语法,而是每次窗口滑动时排序逻辑的一致性——它不像静态分组那样稳定,稍有疏忽,中位数曲线就会出现不可解释的毛刺。










