不能。SQL Server禁止在UPDATE语句的SET子句中直接使用窗口函数,因其仅允许出现在SELECT或ORDER BY子句中;必须通过CTE预计算窗口结果,并基于唯一列JOIN或直接更新CTE来实现安全批处理。

窗口函数不能直接用于 UPDATE 语句
SQL Server 不允许在 UPDATE 语句的 SET 子句中直接使用带 OVER() 的窗口函数。比如下面这种写法会报错:
UPDATE t SET value = AVG(value) OVER (PARTITION BY group_id) FROM my_table t;
错误信息是:Windowed functions can only appear in the SELECT or ORDER BY clauses. —— 窗口函数只允许出现在 SELECT 或 ORDER BY 中。
用 CTE + 窗口函数实现安全的批处理更新
真正可行的做法是把窗口计算结果先“算出来”,再通过 JOIN 或子查询回填到原表。最常用、最清晰的方式是用可更新的 CTE:
- CTE 中执行窗口计算(如
ROW_NUMBER()分批次、AVG() OVER (PARTITION BY ...)计算组内均值等) - CTE 必须引用基表的主键或唯一列,否则 SQL Server 可能拒绝更新
-
UPDATE直接作用于 CTE 别名,不是原始表名
示例:按部门分批更新员工薪资为本部门平均值(每次最多 1000 行)
WITH ranked AS (
SELECT
employee_id,
salary,
AVG(salary) OVER (PARTITION BY dept_id) AS dept_avg,
ROW_NUMBER() OVER (ORDER BY employee_id) AS rn
FROM employees
WHERE salary IS NULL -- 只更新缺失值
)
UPDATE ranked
SET salary = dept_avg
WHERE rn BETWEEN 1 AND 1000;PARTITION BY 和 ORDER BY 对批处理逻辑的影响
窗口函数里的 PARTITION BY 和 ORDER BY 不只是“排序”或“分组”,它们决定了你能否控制更新粒度和一致性:
- 如果用
PARTITION BY dept_id ORDER BY hire_date,那么每个部门内部是独立编号的,适合“每部门最多更新 50 人”这类业务规则 - 如果用
ORDER BY employee_id,则跨部门连续编号,适合全局限流(如“总共只更新前 5000 行”) - 漏掉
ORDER BY会导致ROW_NUMBER()结果不可预测,尤其在并行执行下可能每次不同 -
RANGE帧不适用于批控制;必须用ROWS或无帧(仅用于编号/聚合)
避免隐式类型转换和统计信息失效引发的性能坑
实际跑批时,慢往往不是因为窗口函数本身,而是底层扫描和连接效率被拖垮:
- CTE 中的窗口计算若依赖未索引字段(如
PARTITION BY UPPER(last_name)),会强制扫描全表且无法利用索引 - 更新目标表若缺少针对
WHERE条件的索引(例如上面的WHERE salary IS NULL),CTE 会先物化全部中间结果,内存和 tempdb 压力陡增 - SQL Server 2022 虽支持批处理模式自适应联接,但 CTE 更新路径仍走行模式;大表务必确认执行计划里没出现 “Table Spool” 或 “Sort” 大量数据
- 更新后记得手动更新统计信息:
UPDATE STATISTICS employees (idx_dept_salary);,否则下一批可能选错计划
真正难的不是写出带 OVER 的语句,而是让每一万行更新都稳定在 2 秒内——这取决于分区键是否对齐索引、是否触发了隐式转换、以及 CTE 是否被优化器正确识别为可更新视图。

















