2016-11-17 55 views
1

我有一些格式的數據;智能債務老化代碼

Client Amt Date 
ABC Co £250 20/09/16 
ABC Co £250 20/10/16 
CDE Co £200 20/11/16 
CDE Co £200 20/10/16 
CDE Co £-200 20/09/16 
FGH Co £600 01/01/16 
FGH Co £-500 20/09/16 
FGH Co £-50 20/10/16 
FGH Co £100 20/11/16 

我可以像這樣輕鬆地旋轉它;

Client Balance 0-29days 30-59days 60-89days 90days+ 
ABC Co £500 £0  £250  £250  £0 
CDE Co £200 £200  £200  £-200  £0 
FGH Co £100 £100  £-50  £-500  £600 
IJK Co £-100 £100  £0  £0  £-200 

但我需要它看起來像;

Client Balance 0-29days 30-59days 60-89days 90days+ 
ABC Co £500 £0  £250  £250  £0 
CDE Co £200 £200  £0  £0  £0 
FGH Co £100 £100  £0  £0  £50 
IJK Co £-100 £0  £0  £0  £-100 

列或「老化桶」表示借記/貸記的年齡。單個交易不會發生在多個存儲桶中。如果有信用和借記,他們應該適用於彼此(從最早的開始)。所以要詳細說明一些記錄...

CDE Co; 20/09年最早的交易£200信貸在20/10年下一筆交易£200借方平衡。這隻剩下20/11的200英鎊借記卡(因此0-29天記錄卡里的200英鎊借記卡)。

FGH Co;在01/01的最早交易600英鎊借記是部分支付了£500(20/09)和£-50(20/10)的兩筆支付,在90天+桶內留下50英鎊的借方,最近一次借記20/11在0-29天的時間裏100英鎊。

有沒有可以用來評估這個問題的查詢/公式?或者我將不得不使用遊標?

感謝

+1

這將是如果源數據沒有轉動容易得多。我假設你有一個客戶,日期和表中的數量,並使用日期將數據轉到日期範圍。如果你在數據被旋轉之前這樣做,它會更容易。 – xQbert

+0

我懷疑顯示的數據是查詢的結果。也許你可以提供一個正確的交易數據樣本 –

+0

@xQbert。如果數據沒有被轉移,你將如何處理它? –

回答

1

這是一個解決方案,似乎與您的預期輸出相匹配。請注意,這有點麻煩,你可能可以簡化邏輯一點,但至少它似乎工作。

鏈接到工作示例:http://rextester.com/OWH97326

注意,這個答案是從dba.stackexchange.com一個solution to a slightly similar problem調整。我對這個解決方案印象非常深刻。

Create Table Debt (
    Client char(6), 
    Amount money, 
    [Date] date); 

Insert Into Debt 
Values 
('ABC Co', 250, Convert(date, '20/09/2016', 103)), 
('ABC Co', 250, Convert(date, '20/10/2016', 103)), 
('CDE Co', 200, Convert(date, '20/11/2016', 103)), 
('CDE Co', 200, Convert(date, '20/10/2016', 103)), 
('CDE Co', -200, Convert(date, '20/09/2016', 103)), 
('FGH Co', 600, Convert(date, '01/01/2016', 103)), 
('FGH Co', -500, Convert(date, '20/09/2016', 103)), 
('FGH Co', -50, Convert(date, '20/10/2016', 103)), 
('FGH Co', 100, Convert(date, '20/11/2016', 103)); 

With Grouping_cte As (
Select Client, Sum(ABS(Amount)) As Amount, 
    Case When DateDiff(Day, GetDate(), [Date]) > -30 Then '0-29 days' 
     When DateDiff(Day, GetDate(), [Date]) > -60 Then '30-59 days' 
     When DateDiff(Day, GetDate(), [Date]) > -90 Then '60-89 days' 
     Else '90+ days' End As [Date], 
    Case When Amount < 0 Then 'In' Else 'Out' End As [Type] 
    From Debt 
    Group By Client, 
    Case When DateDiff(Day, GetDate(), [Date]) > -30 Then '0-29 days' 
     When DateDiff(Day, GetDate(), [Date]) > -60 Then '30-59 days' 
     When DateDiff(Day, GetDate(), [Date]) > -90 Then '60-89 days' 
     Else '90+ days' End, 
    Case When Amount < 0 Then 'In' Else 'Out' End), 
RunningTotals_cte As (
Select Client, Amount, [Date], [Type], 
    Sum(Amount) Over (Partition By Client, [Type] Order By [Date] Desc) - Amount As RunningTotalFrom, 
    Sum(Amount) Over (Partition By Client, [Type] Order By [Date] Desc) As RunningTotalTo 
    From Grouping_cte), 
Allocated_cte As (
Select Outs.Client, Outs.Date, Outs.Amount + IsNull(Sum(x.borrowed_qty),0) As AdjustedAmount 
    From (Select * From RunningTotals_cte Where [Type] = 'Out') As Outs 
    Left Join (Select * From RunningTotals_cte Where [Type] = 'In') As Ins 
    On Ins.RunningTotalFrom < Outs.RunningTotalTo 
    And Outs.RunningTotalFrom < Ins.RunningTotalTo 
    And Ins.Client = Outs.Client 
    Cross Apply (
     Select Case When ins.RunningTotalTo < Outs.RunningTotalTo Then Case When ins.RunningTotalFrom > Outs.RunningTotalFrom Then -1 * Ins.Amount 
                      Else -1 * (Ins.RunningTotalTo - Outs.RunningTotalFrom) End 
        Else Case When Outs.RunningTotalFrom > Ins.RunningTotalFrom Then Outs.Amount 
          Else -1 * (Outs.RunningTotalTo - Ins.RunningTotalFrom) End End) As x (borrowed_qty) 
    Group By Outs.Client, Outs.Date, Outs.Amount) 
--Select * From Allocated_cte; 

Select Client, 
    Sum(AdjustedAmount) As Balance, 
    Sum(iif([Date] = '0-29 days', AdjustedAmount, Null)) As [0-29 days], 
    Sum(iif([Date] = '30-59 days', AdjustedAmount, Null)) As [30-59 days], 
    Sum(iif([Date] = '60-89 days', AdjustedAmount, Null)) As [60-89 days], 
    Sum(iif([Date] = '90+ days', AdjustedAmount, Null)) As [90+ days] 
    From Allocated_cte 
    Group By Client; 
-1

如果你只需要你提供的格式的數據,你大約有這個數據在非透視基表的評論說什麼,查詢很簡單:

declare @t table(PaymentDate date 
       ,Client nvarchar(50) 
       ,Amount decimal(10,2) 
       ); 
insert into @t values 
('20160920','ABC Co',250),('20161020','ABC Co',250 ),('20161020','CDE Co',200 ),('20161020','CDE Co',200 ),('20160920','CDE Co',-200),('20160101','FGH Co',600 ),('20160920','FGH Co',-500),('20161020','FGH Co',-100),('20161120','FGH Co',100 ); 

declare @ReportDate date = getdate(); 

select Client 

     -- Data aggregated by each period 
     ,sum(Amount) as ClientBalance 
     ,sum(case when PaymentDate between dateadd(d,-29,@ReportDate) and @ReportDate then Amount else 0 end) as [0-29 Days] 
     ,sum(case when PaymentDate between dateadd(d,-59,@ReportDate) and dateadd(d,-30,@ReportDate) then Amount else 0 end) as [30-59 Days] 
     ,sum(case when PaymentDate between dateadd(d,-89,@ReportDate) and dateadd(d,-60,@ReportDate) then Amount else 0 end) as [60-89 Days] 
     ,sum(case when PaymentDate <= dateadd(d,-90,@ReportDate) then Amount else 0 end) as [90+ Days] 

     ,'' as [ ] 

     -- Data aggregated as a rolling periodic balance 
     ,sum(Amount) as ClientBalance 
     ,sum(case when PaymentDate <= @ReportDate then Amount else 0 end) as [0-29 Days] 
     ,sum(case when PaymentDate <= dateadd(d,-30,@ReportDate) then Amount else 0 end) as [30-59 Days] 
     ,sum(case when PaymentDate <= dateadd(d,-60,@ReportDate) then Amount else 0 end) as [60-89 Days] 
     ,sum(case when PaymentDate <= dateadd(d,-90,@ReportDate) then Amount else 0 end) as [90+ Days] 
from @t 
group by Client 
order by Client; 
+0

要麼我失去了一些東西或你想念的東西。你的查詢看起來像我目前使用的數據透視。其結果看起來像我的第一個例子...但我需要它看起來像我的第二個例子? –

+0

@LeeTickett您能否提供您的源數據和第一個數據透視背後的邏輯?你的問題缺乏基本信息。每列是什麼意思?我上面的代碼目前爲您提供每個日期窗口以及當前的運行平衡。你究竟在追求什麼? – iamdave

+0

目前我的查詢只是給每個老化桶(我相信你的相同)的平衡。但是,如果你看看我的例子,你可以看到有信用和借記的記錄實際上需要一些進一步的工作;例如,CDE Co在60-89天的存儲桶中具有貸方餘額,並在30-59天內具有借方餘額,所以這些可以在0-29天內適用於彼此而僅留下餘額。我將在原始問題中添加源數據的示例。 –

1

鏈接到工作實例:http://rextester.com/NAAUE88941

DECLARE @Table AS TABLE (Client CHAR(6), AMT INT, Date DATE) 
INSERT INTO @Table VALUES 
('ABC Co',250 ,'2016/09/20') 
,('ABC Co',250 ,'2016/10/20') 
,('CDE Co',200 ,'2016/11/20') 
,('CDE Co',200 ,'2016/10/20') 
,('CDE Co',-200,'2016/09/20') 
,('FGH Co',600 ,'2016/01/01') 
,('FGH Co',-500,'2016/09/20') 
,('FGH Co',-50 ,'2016/10/20') 
,('FGH Co',100 ,'2016/11/20') 
,('IJK Co',-100 ,'2016/01/01') 
,('IJK Co',-100 ,'2016/09/20') 

;WITH cte AS (
    SELECT 
     Client 
     ,Date 
     ,AMT 
     ,CurrentBalance = SUM(AMT) OVER (PARTITION BY Client) 
     ,BackwardsRunningTotal = SUM(AMT) OVER (PARTITION BY Client ORDER BY Date DESC) 
     ,CurrentBalanceMinusBackwardsRunningTotal = SUM(AMT) OVER (PARTITION BY Client) - SUM(AMT) OVER (PARTITION BY Client ORDER BY Date DESC) 
     ,DateGroup = CASE 
      WHEN DATEDIFF(day,Date,GETDATE()) BETWEEN 0 AND 29 THEN '0-29days' 
      WHEN DATEDIFF(day,Date,GETDATE()) BETWEEN 30 AND 59 THEN '30-59days' 
      WHEN DATEDIFF(day,Date,GETDATE()) BETWEEN 60 AND 89 THEN '60-89days' 
      WHEN DATEDIFF(day,Date,GETDATE()) >= 90 THEN '90days+' 
      ELSE 'Unknown Error' 
     END 
     ,BalanceAtTime = SUM(AMT) OVER (PARTITION BY Client ORDER BY Date) 
    FROM 
     @Table 
) 

, cteWhenCurrentBalanceIsMet AS (
    SELECT 
     Client 
     ,MaxDate = MAX(DATE) 
    FROM 
     cte 
    WHERE 
     CurrentBalanceMinusBackwardsRunningTotal = 0 
    GROUP BY 
     Client 
) 

, cteAgedDebtPrepared AS (
    SELECT 
     c.Client 
     ,Balance = c.CurrentBalance 
     ,c.DateGroup 
     ,Amt = CASE 
      WHEN CurrentBalanceMinusBackwardsRunningTotal = 0 
      THEN ISNULL(LAG(CurrentBalanceMinusBackwardsRunningTotal) OVER (PARTITION BY c.Client ORDER BY Date DESC),AMT) 
      ELSE AMT 
     END 
    FROM 
     cteWhenCurrentBalanceIsMet m 
     INNER JOIN cte c 
     ON m.Client = c.Client 
     AND m.MaxDate <= c.Date 
     AND SIGN(c.AMT) = SIGN(c.CurrentBalance) 
) 

SELECT * 
FROM 
    cteAgedDebtPrepared 
    PIVOT (
     SUM(Amt) 
     FOR DateGroup IN ([0-29days],[30-59days],[60-89days],[90days+]) 
    ) pvt 
ORDER BY 
    Client 

絕對是一個具有挑戰性的問題,它更是因爲,即使你說你正在尋找在賬齡過長時,您實際上在您的數據透視表中顯示了年老債務和老年債務。我認爲在遞歸CTE中執行操作會更容易,但我希望更多的基於集合的操作,所以我已經提出了這個操作,它適用於所有的測試用例。請注意,我確實添加了一個網絡是Credit的地方。

一般步驟

  • 確定當前餘額
  • 向後運行總計(如SUM(AMT)從最新日期到最早日期)
  • 減去向後運行總計從當前餘額,以確定此時餘額爲0最後再拿到MAX(date)在這所發生
  • 做一個自我加盟抓住所有的記錄>=MAX(date)是相同SIGN(),例如當餘額爲正值時,那麼所有額度必須爲正值或負值,否則爲負值。它必須是相同的SIGN()的原因是反向會實際上影響我們正在尋找的相反方向上的平衡。
  • 找出需要被歸因於第一排,當餘額爲通過查看以前的行或指定的AMT
  • 樞軸最後0根據需要

的債務或剩餘積分結果:

Client Balance 0-29days 30-59days 60-89days 90days+ 
ABC Co 500  NULL  250   250   NULL 
CDE Co 200  200   NULL  NULL  NULL 
FGH Co 150  100   NULL  NULL  50 
IJK Co -200 NULL  NULL  -100  -100 

注意我的IJK示例我有兩個學分,每個-100。

+0

令人驚歎的,謝謝你,我將需要花一點時間,但看起來徹底! –

2

鏈接顯示它的工作:http://rextester.com/MLFE98410

我很好奇,這是比較容易在邏輯上遞歸CTE是有點更容易,但梁具有一些相同的障礙。注意我也在這裏再添加了1個測試用例。

DECLARE @Table AS TABLE (Client CHAR(6), AMT INT, Date DATE) 
INSERT INTO @Table VALUES 
('ABC Co',250 ,'2016/09/20') 
,('ABC Co',250 ,'2016/10/20') 
,('CDE Co',200 ,'2016/11/20') 
,('CDE Co',200 ,'2016/10/20') 
,('CDE Co',-200,'2016/09/20') 
,('FGH Co',600 ,'2016/01/01') 
,('FGH Co',-500,'2016/09/20') 
,('FGH Co',-50 ,'2016/10/20') 
,('FGH Co',100 ,'2016/11/20') 
,('IJK Co',-100 ,'2016/01/01') 
,('IJK Co',-100 ,'2016/09/20') 
,('LMN Co',-200 ,'2016/01/01') 
,('LMN Co', 50 ,'2016/06/10') 
,('LMN Co',-100 ,'2016/09/20') 

;WITH cteRowNumbers AS (
    SELECT *, RowNumber = ROW_NUMBER() OVER (PARTITION BY Client ORDER BY Date DESC) 
    FROM 
     @Table 
) 

, cteRecursive AS (
    SELECT 
     Client 
     ,CurrentBalance = SUM(AMT) 
     ,Date = CAST(GETDATE() AS DATE) 
     ,Amt = CAST(0 AS INT) 
     ,RemainingBalance = SUM(Amt) 
     ,AttributedAmt = 0 
     ,RowNumber = CAST(0 AS BIGINT) 
    FROM 
     @Table 
    GROUP BY 
     Client 

    UNION ALL 

    SELECT 
     r.Client 
     ,r.CurrentBalance 
     ,c.Date 
     ,c.AMT 
     ,CASE WHEN SIGN(r.CurrentBalance) = SIGN(c.AMT) THEN r.CurrentBalance - c.AMT ELSE r.RemainingBalance END 
     ,CASE 
      WHEN SIGN(r.CurrentBalance) <> SIGN(c.AMT) THEN 0 
      WHEN ABS(r.RemainingBalance) < ABS(c.AMT) THEN r.RemainingBalance 
      ELSE c.AMT END 
     ,c.RowNumber 
    FROM 
     cteRecursive r 
     INNER JOIN cteRowNumbers c 
     ON r.Client = c.Client 
     AND r.RowNumber + 1 = c.RowNumber 
    WHERE 
     SIGN(r.RemainingBalance) = SIGN(r.CurrentBalance) 
) 

, ctePrepared AS (
    SELECT 
     Client 
     ,CurrentBalance 
     ,DateGroup = CASE 
      WHEN DATEDIFF(day,Date,GETDATE()) BETWEEN 0 AND 29 THEN '0-29days' 
      WHEN DATEDIFF(day,Date,GETDATE()) BETWEEN 30 AND 59 THEN '30-59days' 
      WHEN DATEDIFF(day,Date,GETDATE()) BETWEEN 60 AND 89 THEN '60-89days' 
      WHEN DATEDIFF(day,Date,GETDATE()) >= 90 THEN '90days+' 
       ELSE 'Unknown Error' 
     END 
     ,AttributedAmt 
    FROM 
     cteRecursive 
    WHERE 
     RowNumber > 0 
     AND AttributedAmt <> 0 
) 

SELECT * 
FROM 
    ctePrepared c 
    PIVOT (
     SUM(AttributedAmt) 
     FOR DateGroup IN ([0-29days],[30-59days],[60-89days],[90days+]) 
    ) pvt 
ORDER BY 
    Client 

結果

Client CurrentBalance 0-29days 30-59days 60-89days 90days+ 
ABC Co 500   NULL   250   250   NULL 
CDE Co 200   200   NULL  NULL   NULL 
FGH Co 150   100   NULL  NULL   50 
IJK Co -200    NULL  NULL -100   -100 
LMN Co -250    NULL  NULL -100   -150 
0

我已經回答了類似的問題hereherehere

你需要爆炸借貸記賬單單元,然後他們夫婦按時間順序,並過濾掉匹配的行,那麼你可以每個時期的年齡。

只是將每個週期的總和翻轉過來。

DECLARE @Table AS TABLE (Client CHAR(6), AMT INT, Date DATE) 
INSERT INTO @Table VALUES 
('ABC Co',250 ,'2016/09/20') 
,('ABC Co',250 ,'2016/10/20') 
,('CDE Co',200 ,'2016/11/20') 
,('CDE Co',200 ,'2016/10/20') 
,('CDE Co',-200,'2016/09/20') 
,('FGH Co',600 ,'2016/01/01') 
,('FGH Co',-500,'2016/09/20') 
,('FGH Co',-50 ,'2016/10/20') 
,('FGH Co',100 ,'2016/11/20') 
,('IJK Co',-200 ,'2016/01/01') 
,('IJK Co',100 ,'2016/09/20') 

對於FN_NUMBERS(N),它是一個符合表,看看我在上面鏈接,以實現一個例子或者google一下其他的答案。

;with 
m as (select * from @Table), 
e as (select * from m where AMT>0), 
r as (select * from m where AMT<0), 
ex as (
    select *, ROW_NUMBER() over (partition by Client order by [date]) rn, 1 q 
    from e 
    join FN_NUMBERS(1000) on N<= e.AMT 
), 
rx as (
    select *, ROW_NUMBER() over (partition by Client order by [date]) rn, 1 q 
    from r 
    join FN_NUMBERS(1000) on N<= -r.AMT 
), 
j as (
select 
    isnull(ex.Client, rx.Client) Client, 
    (datediff(DAY, ISNULL(ex.[Date],rx.[Date]), GETDATE())/30) dd, 
    (isnull(ex.q,0) - isnull(rx.q,0)) q 
from ex 
full join rx on ex.Client = rx.Client and ex.rn = rx.rn 
where ex.Client is null or rx.Client is null 
), 
mm as (
    select j.Client, j.q, isnull(x.n,99) n 
    from j 
    left join (values (0),(1),(2)) x (n) on dd=n 
), 
b as (
    select Client, SUM(AMT) balance 
    from m 
    group by Client 
), 
p as (
    select b.*, p.[0] as [0-12days], p.[1] as [30-59days], p.[2] as [60-89days], p.[99] as [90days+] 
    from mm 
    pivot (sum(q) for n in ([0],[1],[2],[99])) p 
    left join b on p.Client = b.Client 
) 
select * 
from p 
order by 1 

完美的輸出

Client balance 0-12days 30-59days 60-89days 90days+ 
ABC Co 500  NULL  250   250   NULL 
CDE Co 200  200   NULL  NULL  NULL 
FGH Co 150  100   NULL  NULL  50 
IJK Co -100 NULL  NULL  NULL  -100 

再見