我有一个问题,例如在C#代码中很容易解决,但我不知道如何使用递归CTE-s或Sliding-Windows函数编写SQL查询。
以下是这种情况:假设我有一个包含3列(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
我想从表中得到的结果是(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 Server中使用滑动窗口函数。
我需要一个带滑动函数或递归CTE的实现。 不是周期......
感谢。
答案 0 :(得分:3)
好的,想想这就是你想要的。有两个递归查询。一个用于向上传播,第二个用于向下传播。
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
我可以想象你只使用向上传播,第一行的向下传播是用某种程序语言完成的。向下传播是通过少量第一行扫描一次,因此,递归查询可能是蚊子上的锤子。
答案 1 :(得分:1)
我在表中添加客户端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
上进行测试
我认为这种方法比@RadimBača慢。并且它展示了在SQL上实现顺序算法的可能性。