使用滑动窗口函数或递归CTE的SQL实现

时间:2017-11-16 12:09:59

标签: sql sql-server recursion sliding-window

我有一个问题,例如在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的实现。 不是周期......

感谢。

2 个答案:

答案 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

demo

我可以想象你只使用向上传播,第一行的向下传播是用某种程序语言完成的。向下传播是通过少量第一行扫描一次,因此,递归查询可能是蚊子上的锤子。

答案 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

sqlfiddle.com

上进行测试

我认为这种方法比@RadimBača慢。并且它展示了在SQL上实现顺序算法的可能性。