SQL 使用滑动 Window 函数或递归 CTE 实现
SQL Implementation with Sliding-Window functions or Recursive CTEs
我有一个问题,例如在 C# 代码中很容易解决,但我不知道如何使用 Recursive CTE-s 或 Sliding-[=38= 编写 SQL 查询] 函数。
情况如下:假设我有一个包含 3 列的 table (ID, Date, Amount
),这里是一些数据:
ID Date Amount
-----------------------
1 01.01.2016 -500
2 01.02.2016 1000
3 01.03.2016 -200
4 01.04.2016 300
5 01.05.2016 500
6 01.06.2016 1000
7 01.07.2016 -100
8 01.08.2016 200
我想从 table 得到的结果是这样的 (ID, Amount .... Order By Date
):
ID Amount
-----------------------
2 300
4 300
5 500
6 900
8 200
我们的想法是将金额分期付款,分别针对每个客户,但问题是当出现负数额时,您需要从最后一期付款中删除金额。我不知道我有多清楚,所以这里有一个例子:
假设我有一个客户的 3 张发票,金额分别为 500、200、-300。
如果我开始分发这些发票,首先我分发金额 500,然后是 200。但是当我来到第三个 -300 时,我需要从最后一张发票中删除。也就是说 200 - 300 = -100,所以第二张发票的金额会消失,但仍然有 -100 需要从第一张发票中减去。所以 500 - 100 = 400。我需要的结果是一行结果(第一张发票金额为 400)
第一张发票金额为负值(-500、300、500)的另一个例子。
在这种情况下,第一个 (-500) 发票将使第二个发票消失,另外 200 个将从第三个发票中减去。所以结果将是:金额为 300 的第三张发票。
这类似于编程语言中的 Stack 实现,但我需要在 SQL 服务器中使用滑动 window 函数来实现。
我需要一个带有滑动函数或递归 CTE 的实现。
不带循环...
谢谢。
好的,认为这就是你想要的。有两个递归查询。一个用于向上传播,第二个用于向下传播。
with your_data_rn as
(
select *, row_number() over (order by date) rn
from your_data
), rcte_up(id, date, ammount, rn, running) as
(
select *, ammount as running
from your_data_rn
union all
select d.*,
d.ammount + rcte_up.running
from your_data_rn d
join rcte_up on rcte_up.running < 0 and d.rn = rcte_up.rn - 1
), data2 as
(
select id, date, min(running) ammount,
row_number() over (order by date) rn
from rcte_up
group by id, date, rn
having min(running) > 0 or rn = 1
), rcte_down(id, date, ammount, rn, running) as
(
select *, ammount as running
from data2
union all
select d.*, d.ammount + rcte_down.running
from data2 d
join rcte_down on rcte_down.running < 0 and d.rn = rcte_down.rn + 1
)
select id, date, min(running) ammount
from rcte_down
group by id, date
having min(running) > 0
我可以想象你只使用向上传播,而第一行的向下传播是用某种过程语言完成的。向下传播是一次扫描几行第一行,因此,递归查询可能是一个蚊子上的锤子。
我在 table 中添加客户端 ID 以获得更通用的解决方案。然后我在查询字段中实现了存储为 XML 的堆栈。并使用 Recursive-CTE 模拟程序循环:
with Data as( -- Numbering rows for iteration on CTE
select Client, id, Amount,
cast(row_number() over(partition by Client order by Date) as int) n
from TabW
),
CTE(Client, n, stack) as( -- Recursive CTE
select Client, 1, cast(NULL as xml) from Data where n=1
UNION ALL
select D.Client, D.n+1, (
-- Stack operations to process current row (D)
select row_number() over(order by n) n,
-- Use calculated amount in first positive and oldest stack cell
-- Else preserve value stored in stack
case when n=1 or (n=0 and last=1) then new else Amount end Amount,
-- Set ID in stack cell for positive and new data
case when n=1 and D.Amount>0 then D.id else id end id
from (
select Y.Amount, Y.id, new,
-- Count positive stack entries
sum(case when new<=0 or (n=0 and Amount<0) then 0 else 1 end) over (order by n) n,
row_number() over(order by n desc) last -- mark oldest stack cell by 1
from (
select X.*,sum(Amount) over(order by n) new
from (
select case when C.stack.value('(/row/@Amount)[1]','int')<0 then -1 else 0 end n,
D.Amount, D.id -- Data from new record
union all -- And expand current stack in XML to table
select node.value('@n','int') n, node.value('@Amount','int'), node.value('@id','int')
from C.stack.nodes('//row') N(node)
) X
) Y where n>=0 -- Suppress new cell if the stack contained a negative amount
) Z
where n>0 or (n=0 and last=1)
for xml raw, type
)
from Data D, CTE C
where D.n=C.n and D.Client=C.Client
) -- Expand stack into result table
select CTE.Client, node.value('@id','int') id, node.value('@Amount','int')
from CTE join (select Client, max(n) max_n from Data group by Client) X on CTE.Client=X.Client and CTE.n=X.max_n+1
cross apply stack.nodes('//row') N(node)
order by CTE.Client, node.value('@n','int') desc
我觉得这个方法比慢。它展示了在 SQL.
上实现顺序算法的可能性
我有一个问题,例如在 C# 代码中很容易解决,但我不知道如何使用 Recursive CTE-s 或 Sliding-[=38= 编写 SQL 查询] 函数。
情况如下:假设我有一个包含 3 列的 table (ID, Date, Amount
),这里是一些数据:
ID Date Amount
-----------------------
1 01.01.2016 -500
2 01.02.2016 1000
3 01.03.2016 -200
4 01.04.2016 300
5 01.05.2016 500
6 01.06.2016 1000
7 01.07.2016 -100
8 01.08.2016 200
我想从 table 得到的结果是这样的 (ID, Amount .... Order By Date
):
ID Amount
-----------------------
2 300
4 300
5 500
6 900
8 200
我们的想法是将金额分期付款,分别针对每个客户,但问题是当出现负数额时,您需要从最后一期付款中删除金额。我不知道我有多清楚,所以这里有一个例子:
假设我有一个客户的 3 张发票,金额分别为 500、200、-300。
如果我开始分发这些发票,首先我分发金额 500,然后是 200。但是当我来到第三个 -300 时,我需要从最后一张发票中删除。也就是说 200 - 300 = -100,所以第二张发票的金额会消失,但仍然有 -100 需要从第一张发票中减去。所以 500 - 100 = 400。我需要的结果是一行结果(第一张发票金额为 400)
第一张发票金额为负值(-500、300、500)的另一个例子。 在这种情况下,第一个 (-500) 发票将使第二个发票消失,另外 200 个将从第三个发票中减去。所以结果将是:金额为 300 的第三张发票。
这类似于编程语言中的 Stack 实现,但我需要在 SQL 服务器中使用滑动 window 函数来实现。
我需要一个带有滑动函数或递归 CTE 的实现。 不带循环...
谢谢。
好的,认为这就是你想要的。有两个递归查询。一个用于向上传播,第二个用于向下传播。
with your_data_rn as
(
select *, row_number() over (order by date) rn
from your_data
), rcte_up(id, date, ammount, rn, running) as
(
select *, ammount as running
from your_data_rn
union all
select d.*,
d.ammount + rcte_up.running
from your_data_rn d
join rcte_up on rcte_up.running < 0 and d.rn = rcte_up.rn - 1
), data2 as
(
select id, date, min(running) ammount,
row_number() over (order by date) rn
from rcte_up
group by id, date, rn
having min(running) > 0 or rn = 1
), rcte_down(id, date, ammount, rn, running) as
(
select *, ammount as running
from data2
union all
select d.*, d.ammount + rcte_down.running
from data2 d
join rcte_down on rcte_down.running < 0 and d.rn = rcte_down.rn + 1
)
select id, date, min(running) ammount
from rcte_down
group by id, date
having min(running) > 0
我可以想象你只使用向上传播,而第一行的向下传播是用某种过程语言完成的。向下传播是一次扫描几行第一行,因此,递归查询可能是一个蚊子上的锤子。
我在 table 中添加客户端 ID 以获得更通用的解决方案。然后我在查询字段中实现了存储为 XML 的堆栈。并使用 Recursive-CTE 模拟程序循环:
with Data as( -- Numbering rows for iteration on CTE
select Client, id, Amount,
cast(row_number() over(partition by Client order by Date) as int) n
from TabW
),
CTE(Client, n, stack) as( -- Recursive CTE
select Client, 1, cast(NULL as xml) from Data where n=1
UNION ALL
select D.Client, D.n+1, (
-- Stack operations to process current row (D)
select row_number() over(order by n) n,
-- Use calculated amount in first positive and oldest stack cell
-- Else preserve value stored in stack
case when n=1 or (n=0 and last=1) then new else Amount end Amount,
-- Set ID in stack cell for positive and new data
case when n=1 and D.Amount>0 then D.id else id end id
from (
select Y.Amount, Y.id, new,
-- Count positive stack entries
sum(case when new<=0 or (n=0 and Amount<0) then 0 else 1 end) over (order by n) n,
row_number() over(order by n desc) last -- mark oldest stack cell by 1
from (
select X.*,sum(Amount) over(order by n) new
from (
select case when C.stack.value('(/row/@Amount)[1]','int')<0 then -1 else 0 end n,
D.Amount, D.id -- Data from new record
union all -- And expand current stack in XML to table
select node.value('@n','int') n, node.value('@Amount','int'), node.value('@id','int')
from C.stack.nodes('//row') N(node)
) X
) Y where n>=0 -- Suppress new cell if the stack contained a negative amount
) Z
where n>0 or (n=0 and last=1)
for xml raw, type
)
from Data D, CTE C
where D.n=C.n and D.Client=C.Client
) -- Expand stack into result table
select CTE.Client, node.value('@id','int') id, node.value('@Amount','int')
from CTE join (select Client, max(n) max_n from Data group by Client) X on CTE.Client=X.Client and CTE.n=X.max_n+1
cross apply stack.nodes('//row') N(node)
order by CTE.Client, node.value('@n','int') desc
我觉得这个方法比