【问题标题】:SQL monthly statement using recursive cte使用递归 cte 的 SQL 月报表
【发布时间】:2017-12-11 15:47:36
【问题描述】:

我正在尝试从包含所有历史记录的交易表中创建每月银行帐户对帐单。 我希望将期初余额作为第一行,然后使用递归 cte 将当前月份的交易与当时更新的余额进行交易。 我知道这也可以通过表更新来完成,但我正在寻找递归。

表结构如下:

declare @temp table (date datetime,tran_id int,cust_id int,tran_type char,amount int)
insert into @temp values('2017-06-06 22:05:10.703',1,1,'c',700),
('2017-06-12 22:05:10.703',2,1,'d',100),('2017-06-20 22:05:10.703',3,1,'c',200),
('2017-06-26 22:05:10.703',4,1,'d',450),(getdate()+1,5,1,'d',200),
(getdate()+2,6,1,'d',200),(getdate()+3,7,1,'c',500),
(getdate()+4,8,1,'d',300),(getdate()+5,9,1,'d',200),
('2017-06-18 22:05:10.703',12,1,'d',100)

所以这里有第 6 个月和第 7 个月的交易。 当月的期初余额是6月份所有交易的总和,将作为锚点得到递归余额。 现在我希望选择查询将 date,tran_id,cust_id,credit,debit,balance 作为结果集。

所以如果表有这样的数据:

date                   tran_id  cust_id tran_type   amount
2017-06-06 22:05:10.703 1   1   c   700
2017-06-12 22:05:10.703 2   1   d   100
2017-06-20 22:05:10.703 3   1   c   200
2017-06-26 22:05:10.703 4   1   d   450
2017-07-08 16:34:24.817 5   1   d   200
2017-07-09 16:34:24.817 6   1   d   200
2017-07-10 16:34:24.817 7   1   c   500
2017-07-11 16:34:24.817 8   1   d   300
2017-07-12 16:34:24.817 9   1   d   200
2017-06-18 22:05:10.703 12  1   d   100

The monthly statement for month 7 should be like:
opening balance of 250

date                     tran_id    cust_id credit  debit   balance
2017-07-08 16:40:56.810     5        1      NULL     200    50
2017-07-09 16:40:56.810     6        1      NULL     200    -150
2017-07-10 16:40:56.810     7        1      500      NULL   350
2017-07-11 16:40:56.810     8        1      NULL     300    -50
2017-07-12 16:40:56.810     9        1      NULL     200    -250

我尝试过使用递归 cte 和 sum 窗口函数,但它并没有提供连续平衡,只是逐行平衡。

同样在 cte 中使用聚合函数也是不行的。

;with cte as
(
select cust_id,sum(case when tran_type='c' then amount*1 else amount*-1 end) 
as 'opening balance' from @temp 
where MONTH(date)=6 group by cust_id
),

cte2 as
(
select * from cte
union all
select t.cust_id,amount+[opening balance] as 'balance1' from @temp t join 
cte2 c on c.cust_id=t.cust_id
where MONTH(date)=7
)
select * from cte2

;with cte as
(
select cust_id,sum(case when tran_type='c' then amount*1 else amount*-1 end) 
as 'opening balance' from @temp 
where MONTH(date)=6 group by cust_id
union all
select t.cust_id,SUM(amount+[opening balance]) as 'balance1' from @temp t 
join cte c on c.cust_id=t.cust_id
where MONTH(date)=7
)
select * from cte
option (MAXRECURSION 1000)

我错过了什么?

【问题讨论】:

  • 能否以表格形式添加预期结果
  • 添加了预期结果的表格
  • 你使用的是哪个版本的sql server
  • SQL Server 2008

标签: sql sql-server recursion common-table-expression recursive-query


【解决方案1】:

只需使用累积和:

select t.*,
       sum(case when tran_type = 'c' then amount else - amount end) over 
           (partition by cust_id order by date) as balance
from @temp t;

然后您可以使用子查询或 CTE 选择范围:

select t.*
from (select t.*,
             sum(case when tran_type = 'c' then amount else - amount end) over 
                 (partition by cust_id order by date) as balance
      from @temp t
     ) t
where . . .

where 需要一个子查询,所以它不会影响累积和。

编辑:

在 SQL Server 2008 中,您可以使用cross apply

select t.*,
       t2.balance
from @temp t cross apply
     (select sum(case when t2.tran_type = 'c' then t2.amount else - t2.amount end) as balance
      from @temp t2
      where t2.cust_id = t.cust_id and t2.date <= t.date
     ) t2;

【讨论】:

  • 这很有帮助,但我正在寻找 SQL Server 2008 的解决方案
【解决方案2】:

对于旧版本,您可以使用CROSS APPLY计算运行余额

;WITH opening_balance_cte
     AS (SELECT cust_id,
                Sum(CASE WHEN tran_type = 'c' THEN amount ELSE -amount END) AS opening_balance
         FROM   Yourtable
         WHERE  [date] < '2017-07-01'
         GROUP  BY cust_id)
SELECT *,
       balance
FROM   Yourtable a
       JOIN opening_balance_cte ob
         ON ob.cust_id = a.cust_id
       CROSS apply (SELECT ob.opening_balance + Sum(CASE WHEN tran_type = 'c' THEN amount ELSE -amount END) AS balance
                    FROM   Yourtable b
                    WHERE  a.cust_id = b.cust_id
                           AND a.tran_id >= b.tran_id
                           AND b.[date] >= '2017-07-01'
                           AND b.[date] < '2017-08-01') cs
WHERE  a.[date] >= '2017-07-01'
       AND a.[date] < '2017-08-01' 

如果你真的想知道如何使用Recursive CTE 来实现这一点,那么

;WITH opening_balance_cte
     AS (SELECT cust_id,
                Sum(CASE WHEN tran_type = 'c' THEN amount ELSE -amount END) AS opening_balance
         FROM   @temp
         WHERE  [date] < '2017-07-01'
         GROUP  BY cust_id),
     rec_cte
     AS (SELECT TOP 1 a.*,
                      opening_balance + CASE WHEN tran_type = 'c' THEN amount ELSE -amount END AS balance
         FROM   @temp a
                JOIN opening_balance_cte o
                  ON a.cust_id = o.cust_id
         WHERE  a.[date] >= '2017-07-01'
                AND a.[date] < '2017-08-01'
         ORDER  BY a.[date] ASC
         UNION ALL
         SELECT t.*,
                rc.balance + CASE WHEN t.tran_type = 'c' THEN t.amount ELSE -t.amount END
         FROM   rec_cte rc
                JOIN @temp t
                  ON t.cust_id = rc.cust_id
                     AND t.tran_id = rc.tran_id + 1)
SELECT *
FROM   rec_cte 

【讨论】:

  • 是的..这行得通..如果我错了,请纠正我,但使用递归 cte 无法做到这一点,对吗?因为 group by 子句..
  • @AnisShaikh - 我们可以使用Recursive CTE,但这是更好的方法
  • @AnisShaikh - 更新为 Recursive CTE 答案
  • 是的,这就是我打算做的......确实使用了两个 ctes,但在第二个 ctes 中也使用了聚合......完美......感谢您抽出时间......@prdp
  • @AnisShaikh - 很高兴它有帮助.. 玩得开心 ;)
猜你喜欢
  • 2012-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多