2016-11-17 23 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 

再见