clickhouse自v21.9起正式支持窗口函数,v21.3–v21.8需设allow_experimental_window_functions=1;sum()over(order by...)是唯一可靠累计写法,缺order by则结果不可控且非逐行累加。

ClickHouse 从 v21.9 起已正式支持窗口函数,SUM() OVER() 是最直接、最高效的累计求和方式——但必须注意:不加 ORDER BY 会导致结果不可控,且旧版本(v21.3–v21.8)需手动开启实验开关。
确认版本并启用窗口函数(v21.3–v21.8 必做)
低于 v21.9 的版本默认禁用窗口函数,执行前必须显式开启:
- 临时启用(当前 session 有效):
SET allow_experimental_window_functions = 1 - 永久启用:在
users.xml的对应 profile 下添加<allow_experimental_window_functions>1</allow_experimental_window_functions> - 验证是否生效:
SELECT * FROM system.settings WHERE name = 'allow_experimental_window_functions',返回值应为1
注意:v21.9+ 已移除该限制,无需任何设置;若查询报错 Unknown function SUM OVER,大概率是版本过低或未开启。
SUM() OVER(ORDER BY ...) 是唯一可靠的累计写法
累计求和本质依赖顺序,SUM(salary) OVER()(无 ORDER BY)会把整个分区当做一个无序集合,结果等价于普通聚合,**不是逐行累加**。
- 正确写法(按时间逐行累计):
SUM(salary) OVER (ORDER BY month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - 简写等效(推荐):
SUM(salary) OVER (ORDER BY month)—— ClickHouse 默认使用RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,但对时间列建议显式用ROWS避免同值歧义 - 错误写法:
SUM(salary) OVER (PARTITION BY department)—— 缺少ORDER BY,无法形成累计语义
示例(基于 employee_salary_1 表):
SELECT month, name, salary, SUM(salary) OVER (ORDER BY month, name) AS cumsum_by_month_name FROM employee_salary_1 ORDER BY month, name;
按月分组累计 vs 全局累计:PARTITION BY 的实际影响
PARTITION BY 决定累计范围边界,它不是“分组后各自累计”的直觉逻辑,而是**重置累计起点**:
- 全局累计(不分组):
SUM(salary) OVER (ORDER BY month)—— 所有行按month排序后连续累加 - 按部门累计:
SUM(salary) OVER (PARTITION BY department ORDER BY month)—— 每个department内独立从 0 开始累计,跨部门不延续 - 混合场景(如“每个部门内按时间累计,再在整个部门内取最大值”)可嵌套使用:
MAX(SUM(salary) OVER (ORDER BY month)) OVER (PARTITION BY department)
注意:PARTITION BY 后必须跟 ORDER BY,否则累计无意义;且 PARTITION BY 列不能出现在 ORDER BY 中(语法报错)。
性能与数据倾斜风险:ORDER BY 列的选择很关键
累计窗口的性能高度依赖 ORDER BY 列的基数与分布:
- 高基数时间列(如
DateTime精确到秒):排序开销小,ROWS帧精准,推荐 - 低基数列(如
department只有 3–5 个值):大量同行被视为peers,RANGE帧可能意外包含多行,导致累计值跳变 - 避免用字符串列(如
name)单独排序做累计:排序成本高,且业务上通常无序意义 - 若需按数值大小累计(如销售额排名),先用
ORDER BY sales DESC,但注意这会改变物理顺序,可能影响后续过滤逻辑
真实瓶颈往往不在窗口函数本身,而在 ORDER BY 引发的全局排序 —— 如果数据已按目标列排序(如表引擎 ORDER BY month),ClickHouse 能跳过部分排序步骤,提速明显。
真正容易被忽略的是:累计结果依赖完整有序集,任何 WHERE 条件都必须在窗口计算**之前**完成过滤(即不能在 OVER() 内写条件),否则会漏掉前置行导致累计断裂。需要条件累计时,务必用子查询或 CTE 预过滤。










