智能债务老化代码

时间:2016-11-17 15:43:56

标签: sql sql-server pivot aggregate-functions

我有一些格式为;

的数据
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)的2笔付款,在90天+桶中留下50英镑的借记和最近的借记在0-29天的时间里,20/11的价格为100英镑。

我可以使用查询/公式来评估此问题吗?或者我将不得不使用光标?

由于

5 个答案:

答案 0 :(得分:2)

链接显示正常工作:http://rextester.com/MLFE98410

我很好奇这在逻辑上更容易,递归cte有点容易,但是sill有一些相同的障碍。注意我在这里添加了另外一个测试用例。

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

答案 1 :(得分:1)

链接到工作示例:http://rextester.com/NAAUE88941

MAX(date)

肯定是一个具有挑战性的问题更是如此,因为即使你说你正在看老年债务,你实际上在你的数据透视表中显示了老年债务和老年信贷。我认为在递归CTE中更容易做,但我想要更多基于集合的操作,所以上面是我提出的,它适用于所有的测试用例。注意我确实在网上添加了一个信用卡。

一般步骤

  • 确定当前余额
  • 向后运行总计(例如,SUM(AMT)从最新日期到最早的日期)
  • 从当前余额中减去向后运行总计,以确定余额最后为0的点,然后获取发生的>=
  • 自我加入以获取MAX(date) SIGN() 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 的所有记录CurrentDataTop,例如当余额为正数时,则必须为正数或反向负数,否则为负数。它必须是相同的SIGN()的原因是反向实际上会影响我们正在寻找的相反方向的平衡。
  • 计算当余额为最后一个0时,通过查看上一行或指定AMT,需要归入第一行的债务或信贷的剩余部分
  • Pivot As Desired

结果:

Offset

注意我的IJK例子我有两个积分-100。

答案 2 :(得分: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;

答案 3 :(得分:0)

我已经回答了类似问题herehere以及here

您需要将借记和点数分成单个单位,然后按时间顺序排列,并过滤掉匹配的行,然后您可以按期间对其进行老化。

只需为每个时段调整总和。

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),它是一个计数表,请查看我上面链接的其他答案以获取示例或谷歌。

;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

再见

答案 4 :(得分:-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;