SQL 使用递归 cte 的月结单
SQL monthly statement using recursive cte
我正在尝试根据包含所有历史记录的交易 table 创建月度银行账户对帐单。
我希望将期初余额作为第一行,然后使用递归 cte 将当前月份的交易与当时更新的余额进行交易。
我知道这也可以通过 table 更新来完成,但我正在寻找递归。
table 结构如下:
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 月份所有交易的总和,它将用作获得递归余额的锚点。
现在我希望 select 查询有日期,tran_id,cust_id,贷方,借方,余额作为结果集。
所以如果 table 有这样的数据:
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 和窗口求和函数,但它并不能提供连续的平衡,只是逐行平衡。
在 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)
我错过了什么?
只需使用累计和:
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;
然后您可以 select 使用子查询或 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;
对于旧版本,您可以使用 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
我正在尝试根据包含所有历史记录的交易 table 创建月度银行账户对帐单。 我希望将期初余额作为第一行,然后使用递归 cte 将当前月份的交易与当时更新的余额进行交易。 我知道这也可以通过 table 更新来完成,但我正在寻找递归。
table 结构如下:
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 月份所有交易的总和,它将用作获得递归余额的锚点。 现在我希望 select 查询有日期,tran_id,cust_id,贷方,借方,余额作为结果集。
所以如果 table 有这样的数据:
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 和窗口求和函数,但它并不能提供连续的平衡,只是逐行平衡。
在 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)
我错过了什么?
只需使用累计和:
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;
然后您可以 select 使用子查询或 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;
对于旧版本,您可以使用 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