我正在尝试计算运行总数。但是当累积和大于另一列值时它应该重置
create table #reset_runn_total
(
id int identity(1,1),
val int,
reset_val int,
grp int
)
insert into #reset_runn_total
values
(1,10,1),
(8,12,1),(6,14,1),(5,10,1),(6,13,1),(3,11,1),(9,8,1),(10,12,1)
SELECT Row_number()OVER(partition BY grp ORDER BY id)AS rn,*
INTO #test
FROM #reset_runn_total
索引详情:
CREATE UNIQUE CLUSTERED INDEX ix_load_reset_runn_total
ON #test(rn, grp)
样本数据
+----+-----+-----------+-----+
| id | val | reset_val | Grp |
+----+-----+-----------+-----+
| 1 | 1 | 10 | 1 |
| 2 | 8 | 12 | 1 |
| 3 | 6 | 14 | 1 |
| 4 | 5 | 10 | 1 |
| 5 | 6 | 13 | 1 |
| 6 | 3 | 11 | 1 |
| 7 | 9 | 8 | 1 |
| 8 | 10 | 12 | 1 |
+----+-----+-----------+-----+
预期结果
+----+-----+-----------------+-------------+
| id | val | reset_val | Running_tot |
+----+-----+-----------------+-------------+
| 1 | 1 | 10 | 1 |
| 2 | 8 | 12 | 9 | --1+8
| 3 | 6 | 14 | 15 | --1+8+6 -- greater than reset val
| 4 | 5 | 10 | 5 | --reset
| 5 | 6 | 13 | 11 | --5+6
| 6 | 3 | 11 | 14 | --5+6+3 -- greater than reset val
| 7 | 9 | 8 | 9 | --reset -- greater than reset val
| 8 | 10 | 12 | 10 | --reset
+----+-----+-----------------+-------------+
询问:
我使用Recursive CTE
. 原始问题在这里https://stackoverflow.com/questions/42085404/reset-running-total-based-on-another-column
;WITH cte
AS (SELECT rn,id,
val,
reset_val,
grp,
val AS running_total,
Iif (val > reset_val, 1, 0) AS flag
FROM #test
WHERE rn = 1
UNION ALL
SELECT r.*,
Iif(c.flag = 1, r.val, c.running_total + r.val),
Iif(Iif(c.flag = 1, r.val, c.running_total + r.val) > r.reset_val, 1, 0)
FROM cte c
JOIN #test r
ON r.grp = c.grp
AND r.rn = c.rn + 1)
SELECT *
FROM cte
T-SQL
在不使用. 的情况下有没有更好的选择CLR
?
我已经研究过类似的问题,但从未能够找到对数据进行单次传递的窗口函数解决方案。我不认为这是可能的。窗口函数需要能够应用于列中的所有值。这使得像这样的重置计算非常困难,因为一次重置会更改所有以下值的值。
考虑这个问题的一种方法是,如果你计算一个基本的运行总计,只要你能从正确的前一行中减去运行总计,你就可以获得你想要的最终结果。例如,在您的示例数据中,
id
4 的值是running total of row 4 - the running total of row 3
. 6的值id
是running total of row 6 - the running total of row 3
因为尚未发生重置。id
7的值是running total of row 7 - the running total of row 6
等。我会在一个循环中使用 T-SQL 来解决这个问题。我有点得意忘形,认为我有一个完整的解决方案。对于 300 万行和 500 个组,代码在我的桌面上在 24 秒内完成。我正在使用具有 6 个 vCPU 的 SQL Server 2016 开发人员版进行测试。我一般利用并行插入和并行执行,因此如果您使用的是旧版本或有 DOP 限制,您可能需要更改代码。
在我用来生成数据的代码下方。范围应该与您的样本数据相似
VAL
。RESET_VAL
算法如下:
1)首先将具有标准运行总计的所有行插入到临时表中。
2)在一个循环中:
2a) 对于每个组,计算表中剩余的运行总计高于 reset_value 的第一行,并将 id、太大的运行总计和之前太大的运行总计存储在临时表中。
2b) 将第一个临时表中的行删除到一个结果临时表中,该表的值
ID
小于或等于ID
第二个临时表中的 。根据需要使用其他列调整运行总计。3)删除后不再处理的行再运行
DELETE OUTPUT
到结果表中。这适用于组末尾从不超过重置值的行。我将逐步介绍上述算法在 T-SQL 中的一种实现。
首先创建一些临时表。
#initial_results
保存具有标准运行总计的原始数据,在#group_bookkeeping
每个循环中更新以确定可以移动哪些行,并#final_results
包含针对重置调整的运行总计的结果。之后我在临时表上创建聚集索引,以便可以并行完成插入和索引构建。在我的机器上产生了很大的不同,但在你的机器上可能没有。在源表上创建索引似乎没有帮助,但这可能对您的机器有所帮助。
下面的代码在循环中运行并更新簿记表。对于每个组,我们需要找到
ID
应该移动到结果表中的最大值。我们需要该行的运行总计,以便我们可以从初始运行总计中减去它。grp_done
当不再需要为 a 做任何工作时,该列设置为 1grp
。真的不是
LOOP JOIN
一般提示的粉丝,但这是一个简单的查询,它是获得我想要的最快的方法。为了真正优化响应时间,我想要并行嵌套循环连接而不是 DOP 1 合并连接。下面的代码在循环中运行并将数据从初始表移动到最终结果表中。注意对初始运行总计的调整。
为了您的方便,下面是完整的代码:
使用光标:
在这里查看:http ://rextester.com/WSPLO95303
不是窗口化的,而是纯 SQL 版本:
我不是 SQL Server 方言方面的专家。这是 PostrgreSQL 的初始版本(如果我理解正确,我不能在 SQL Server 的递归部分中使用 LIMIT 1 / TOP 1):
似乎您有几个查询/方法来解决问题,但您没有提供给我们 - 甚至没有考虑过?- 表上的索引。
表中有哪些索引?它是堆还是有聚集索引?
添加此索引后,我会尝试建议的各种解决方案:
或者只是将聚集索引更改(或制作)为
(grp, id)
.拥有一个针对特定查询的索引应该可以提高大多数方法的效率——如果不是所有方法的话。