如何用T-SQL基于上一行计算值更新表中Results字段?
需求说明
需要对临时表#tmp中的Results字段进行递推计算,规则为:
- 每个
Date分组内按Month升序排列 - 若当前行
Results已有值,则直接使用该值 - 若当前行
Results为NULL,则等于上一行的Results值 + 当前行的Increment值
原始数据集的SQL定义:
DROP TABLE IF EXISTS #tmp CREATE TABLE #tmp ( Date DATE , Month INT , Increment FLOAT , Results FLOAT ) INSERT INTO #tmp(Date, Month, Increment, Results) VALUES ('7/1/2022', 0, 0.0027347877960046, 0.00439631056653702) , ('7/1/2022', 1, 0.0332610867687839, NULL) , ('7/1/2022', 2, 0.0541567096339919, NULL) , ('7/1/2022', 3, 0.0534245249728661, NULL) , ('7/1/2022', 4, 0.0497604938051764, NULL) , ('7/1/2022', 5, 0.0448266874224477, NULL) , ('7/1/2022', 6, 0.0637221774467554, NULL) , ('7/1/2022', 7, 0.0953341922962425, NULL) , ('7/1/2022', 8, 0.117940928214655, NULL) , ('7/1/2022', 9, 0.0955895317176205, NULL) , ('6/1/2022', 0, 0.0027347877960046, 0.00439631056653702) , ('6/1/2022', 1, 0.0332610867687839, 0.00752724387918406) , ('6/1/2022', 2, 0.0541567096339919, NULL) , ('6/1/2022', 3, 0.0534245249728661, NULL) , ('6/1/2022', 4, 0.0497604938051764, NULL) , ('6/1/2022', 5, 0.0448266874224477, NULL) , ('6/1/2022', 6, 0.0637221774467554, NULL) , ('6/1/2022', 7, 0.0953341922962425, NULL) , ('6/1/2022', 8, 0.117940928214655, NULL) , ('6/1/2022', 9, 0.0955895317176205, NULL)
期望的计算结果示例:
Date Month Increment Results 6/1/2022 0 0.002734788 0.004396311 6/1/2022 1 0.033261087 0.007527244 6/1/2022 2 0.05415671 0.061683954 6/1/2022 3 0.053424525 0.115108478 6/1/2022 4 0.049760494 0.164868972 6/1/2022 5 0.044826687 0.20969566 6/1/2022 6 0.063722177 0.273417837 6/1/2022 7 0.095334192 0.368752029 6/1/2022 8 0.117940928 0.486692958 6/1/2022 9 0.095589532 0.582282489 7/1/2022 0 0.002734788 0.004396311 7/1/2022 1 0.033261087 0.037657397 7/1/2022 2 0.05415671 0.091814107 7/1/2022 3 0.053424525 0.145238632 7/1/2022 4 0.049760494 0.194999126 7/1/2022 5 0.044826687 0.239825813 7/1/2022 6 0.063722177 0.303547991 7/1/2022 7 0.095334192 0.398882183 7/1/2022 8 0.117940928 0.516823111 7/1/2022 9 0.095589532 0.612412643
最优实现方案
使用**递归CTE(公共表表达式)**是最直接且高效的方式,它可以按分组逐行递推计算,完美适配“用上一行结果计算当前值”的需求,同时能正确处理分组内存在多个非NULL Results的情况。
具体SQL代码如下:
WITH RecursiveCTE AS ( -- 锚点成员:取每个Date分组中最小Month的行,保留原始Results SELECT Date, Month, Increment, Results FROM #tmp WHERE Month = (SELECT MIN(Month) FROM #tmp t WHERE t.Date = #tmp.Date) UNION ALL -- 递归成员:按Month顺序,逐行计算Results SELECT t.Date, t.Month, t.Increment, -- 若当前行Results不为NULL则直接用,否则用上一行Results加当前Increment CASE WHEN t.Results IS NOT NULL THEN t.Results ELSE r.Results + t.Increment END AS Results FROM #tmp t INNER JOIN RecursiveCTE r ON t.Date = r.Date AND t.Month = r.Month + 1 ) -- 输出最终结果,按Date和Month排序 SELECT Date, Month, ROUND(Increment, 9) AS Increment, ROUND(Results, 9) AS Results FROM RecursiveCTE ORDER BY Date DESC, Month;
代码说明
- 锚点成员:筛选每个
Date分组中Month最小的行,作为递推的起始点,保留原始的Results值。 - 递归成员:通过自连接,将当前行与同分组中
Month小1的行关联,根据规则计算当前行的Results:如果当前行已有值则直接使用,否则用上一行的Results加上当前行的Increment。 - 最终输出:对结果按
Date和Month排序,并对数值进行四舍五入,与期望结果格式一致。
替代方案:窗口函数(适用于无中间非NULL Results的场景)
如果每个Date分组中只有第一行有Results,后续全为NULL,也可以用窗口函数的累计求和实现:
SELECT Date, Month, ROUND(Increment, 9) AS Increment, ROUND( FIRST_VALUE(Results) OVER (PARTITION BY Date ORDER BY Month) + SUM(Increment) OVER (PARTITION BY Date ORDER BY Month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - Increment, -- 减去当前行的Increment,因为第一行的Results已经包含初始值 9 ) AS Results FROM #tmp ORDER BY Date DESC, Month;
但这种方案无法处理分组内中间行存在非NULL Results的情况(比如示例中6/1/2022的Month1有值),因此递归CTE是更通用的最优解。
内容的提问来源于stack exchange,提问作者Rob Ong
相关产品推荐
相关产品推荐

