我有一种情况,代表未结余额和贷方的表都包含在一个表中。我需要做的是将所有未清信用额(最好是从最早开始的顺序,但不是必需的)应用到所有未清余额(从最早开始的顺序)。
例如(负余额代表贷方)
Account_ID DateOfEntry Balance
---------- ----------- -------
1 1/1/2012 10.00
1 1/2/2012 -15.00
2 1/1/2012 -15.00
2 1/2/2012 10.00
3 1/1/2012 10.00
3 1/2/2012 1.00
3 1/3/2012 -5.00
4 1/1/2012 5.00
4 1/2/2012 5.00
4 1/3/2012 -7.00
5 1/1/2012 10.00
5 1/2/2012 -5.00
5 1/3/2012 -5.00
会变成:
Account_ID DateOfEntry Balance
---------- ----------- -------
1 1/2/2012 -5.00
2 1/1/2012 -5.00
3 1/1/2012 5.00
3 1/2/2012 1.00
4 1/2/2012 2.00
这是发生的事情的细目
- 帐户 1 和帐户 2 留下信用(表明付款顺序和信用相对于彼此无关紧要)
- 帐户 3 留下了两个余额(表明信用首先应用于最旧的余额)
- 帐户 4 在 2012 年 1 月 2 日还剩一笔余额(表明如果第一个余额不满足信用额度,信用额度将应用于下一个最旧的余额)
- 账户 5 消失了,因为贷方与余额完全匹配。
这是我的真实表中相关列的架构
CREATE TABLE [dbo].[IDAT_AR_BALANCES](
[cvtGUID] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[CLIENT_ID] [varchar](11) NOT NULL,
[AGING_DATE] [datetime] NOT NULL,
[AMOUNT] [money] NOT NULL,
CONSTRAINT [PK_IDAT_ARBALANCES] PRIMARY KEY CLUSTERED ([cvtGUID] ASC)
)
目前我用一个游标循环所有可用的学分来做到这一点。
--Remove AR that totals to 0.
DELETE FROM IDAT_AR_BALANCES
WHERE client_id IN (
SELECT client_id
FROM IDAT_AR_BALANCES
GROUP BY client_id
HAVING SUM(amount) = 0)
--Spred the credits on to existing balances.
select * into #balances from [IDAT_AR_BALANCES] where amount > 0
select * into #credits from [IDAT_AR_BALANCES] where amount < 0
declare credit_cursor cursor for select [CLIENT_ID], amount, cvtGUID from #credits
open credit_cursor
declare @client_id varchar(11)
declare @credit money
declare @balance money
declare @cvtGuidBalance uniqueidentifier
declare @cvtGuidCredit uniqueidentifier
fetch next from credit_cursor into @client_id, @credit, @cvtGuidCredit
while @@fetch_status = 0
begin
--While balances exist for the current client_ID and there are still credits to be applied, loop.
while(@credit < 0 and (select count(*) from #balances where @client_id = CLIENT_ID and amount <> 0) > 0)
begin
--Find the oldest oustanding balance.
select top 1 @balance = amount, @cvtGuidBalance = cvtGuid
from #balances
where @client_id = CLIENT_ID and amount <> 0
order by AGING_DATE
-- merge the balance and the credit
set @credit = @balance + @credit
--If the credit is now postive save the leftover in the currently selected balance and set the credit to 0
if(@credit > 0)
begin
update #balances set amount = @credit where cvtGuid = @cvtGuidBalance
set @credit = 0
end
else -- Credit is larger than the balance, 0 out the balance and continue processesing
update #balances set amount = 0 where cvtGuid = @cvtGuidBalance
end -- end of while loop
--There are no more balances to apply the credit to, save it back to the list.
update #credits set amount = @credit where cvtGuid = @cvtGuidCredit
--Get the next credit.
fetch next from credit_cursor into @client_id, @credit, @cvtGuidCredit
end
close credit_cursor
deallocate credit_cursor
--Delete any balances and credits that where 0'ed out durning the spred negitive.
delete #balances where AMOUNT = 0
delete #credits where AMOUNT = 0
truncate table [IDAT_AR_BALANCES]
insert [IDAT_AR_BALANCES] select * from #balances
insert [IDAT_AR_BALANCES] select * from #credits
drop table #balances
drop table #credits
我确信有更好的方法可以做到这一点,所以我可以在没有光标的情况下做到这一点并从中获得更好的性能,但我很难弄清楚如何在不使用光标。
通常对于诸如运行总计之类的操作,使用游标实际上比其他一些方法更有效。不要害怕使用游标,除非您看到明显的性能问题。
在 SQL Server 2012 中,有新的窗口函数使它变得更好,但显然你不能使用它们。有一种古怪的更新方法“有效”,但该语法不受官方支持,并且在 SQL Server 2012 之后可能无法正常工作——它可能有一天会成为非法语法,并且永远无法保证排序的工作方式。典型的基于集合的方法不能很好地扩展——使用游标通常会更好,因为您只需扫描每一行一次,而使用基于集合的解决方案,扫描会非线性增长。我希望我能向您展示我在这方面所做的工作,但揭示这一切的博客文章的发布日期仍然未知。
至少在声明游标时使用:
这也可能是有用的阅读:
多亏了 Aaron 的建议,我没有尝试让整个事物都基于集合,而是尝试在集合最适用的地方做集合,然后对其余部分使用游标。
我还能够改变必须只从最旧的信用中提取的要求,现在我可以将每个客户的信用汇总到一个桶中并与之对应。
现在快多了,这是更新版本