Sql-Server

在數據集上執行“傳播負數”

  • January 23, 2018

我有一種情況,表示未結餘額和貸方的表都包含在一個表中。我需要做的是將所有未償還的學分(最好按最舊的順序,但不是必需的)應用到所有未結餘額(按最舊的優先順序)。

例如(負餘額代表信用)

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 之後可能無法正常工作 - 它可能有一天會成為非法語法,並且無法保證排序的工作方式。典型的基於集合的方法不能很好地擴展 - 使用游標通常會更好,因為您只需掃描每一行一次,而使用基於集合的解決方案,掃描會非線性增長。我希望我能向您展示我在這方面所做的工作,但是將揭示這一切的部落格文章的發布日期仍然未知。

至少在聲明游標時使用:

DECLARE ... CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY

這也可能是有用的閱讀:

引用自:https://dba.stackexchange.com/questions/18089