考虑下表:
ID | GROUP_ID | ORDER_VAL | RESET_VAL | VAL
---+----------+-----------+-----------+-----
1 | 1 | 1 | (null) | 3
2 | 1 | 2 | (null) | 2
3 | 1 | 3 | (null) | 1
4 | 1 | 4 | 4 | 2
5 | 1 | 5 | (null) | 1
6 | 2 | 1 | (null) | 4
7 | 2 | 2 | 2 | 3
8 | 2 | 3 | (null) | 4
9 | 2 | 4 | (null) | 2
10 | 2 | 5 | (null) | 2
11 | 2 | 6 | (null) | 4
12 | 2 | 7 | 14 | 2
13 | 2 | 8 | (null) | 2
对于每一行,我需要计算VAL
所有先前行的累积总和(按 排序ORDER_VAL
和分组GROUP_ID
),但每次NULL
RESET_VAL
遇到非时,我需要使用该值作为总和。接下来的行也需要建立在RESET_VAL
而不是使用实际总和之上。请注意,每个组可以有多个重置值。
这是我对上表的预期结果:
ID | GROUP_ID | ORDER_VAL | RESET_VAL | VAL | CUMSUM
---+----------+-----------+-----------+-----+-------
1 | 1 | 1 | (null) | 3 | 0
2 | 1 | 2 | (null) | 2 | 3
3 | 1 | 3 | (null) | 1 | 5
4 | 1 | 4 | 4 | 2 | 4
5 | 1 | 5 | (null) | 1 | 6
6 | 2 | 1 | (null) | 4 | 0
7 | 2 | 2 | 2 | 3 | 2
8 | 2 | 3 | (null) | 4 | 5
9 | 2 | 4 | (null) | 2 | 9
10 | 2 | 5 | (null) | 2 | 11
11 | 2 | 6 | (null) | 4 | 13
12 | 2 | 7 | 14 | 2 | 14
13 | 2 | 8 | (null) | 2 | 16
如果不是重置值,我可以使用窗口查询:
SELECT temp.*,
COALESCE(SUM(val) OVER (PARTITION BY group_id ORDER BY order_val ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING),
0) AS cumsum
FROM temp;
我最初错误地认为我可以放在RESET_VAL
的开头COALESCE
,但这不起作用,因为它不会重置后续行的值。
我也尝试了这个解决方案,但它只会重置为零,而不是列中的值。将其调整为这样做证明是不平凡的,因为该值必须传播到所有后续行。
递归查询似乎很自然,但我还没有弄清楚如何做到这一点。
我可能应该提一下,我实际上必须处理的表比上面的示例要大得多(数十万到几百万行),所以请说明是否存在任何答案的性能缺陷。
以下工作,但可能有一些更聪明的版本。查询逻辑说明:
我们首先通过计算列的非空值来确定当前行已经完成了多少“重置”
reset_val
,因此我们可以将行分成子组。我们还使用了另一个窗口函数
LAST_VALUE()
,IGNORE NULLS
所以我们可以找到最后一个reset_value
。请注意,这两个窗口函数
COUNT()
都有LAST_VALUE()
一个ORDER BY
,因此是默认窗口ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
。在查询中省略,使代码更清晰。假设
val
不可为空,则其他窗口函数也可以缩短,从:(也避免
COALESCE()
)到:最后,在第二个 cte 中,我们使用上面找到的子组(使用
PARTITION BY group_id, reset_count
)来找到累积和。在SQLfiddle进行测试。
另一种变体,基于@Chris' recursive answer。(略有改进,与 non-consecutive 一起使用
order_val
,避免使用 finalGROUP BY
)。也适用于组的第一行有一个
reset_val
:在SQLfiddle-2进行测试。
另一种变体,使用旧的(专有)
CONNECT BY
语法进行递归查询。更紧凑,但我发现它比 CTE 版本更难写和读:在SQLfiddle-3测试。
作为一项学术练习,我在 Postgres 中实现了一个解决方案。现在,我知道这是一个关于 Oracle 的问题,但它也被框定为一个 SQL 问题!:) 如果有人更熟悉 Oracle 的递归查询语法,也许他们可以找到必要的更改以使其在 Oracle 中运行。
递归查询(在 Postgres 中)
在这个解决方案中,我使用递归查询,
WITH RECURSIVE
在我的 CTE 中应用 Postgres 语法。递归查询如下:这是相应的SQL Fiddle。
当然,此查询正确行为的关键是首先使用查询的递归部分来构建累积总和,同时确保用于
COALESCE
替换任何RESET_VAL
指示CUMSUM
行的内容。最后一个SELECT
只是让您过滤 的最大值,CUMSUM
因为如果查询构建正确,则最大值与累积和相同。编辑:递归查询的 Oracle 版本
此对 Oracle 的翻译由ypercube提供:
Oracle SQL 小提琴