Sql-Server
在數據集上執行“傳播負數”
我有一種情況,表示未結餘額和貸方的表都包含在一個表中。我需要做的是將所有未償還的學分(最好按最舊的順序,但不是必需的)應用到所有未結餘額(按最舊的優先順序)。
例如(負餘額代表信用)
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
這也可能是有用的閱讀: