我有一些格式为;
的数据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英镑。
我可以使用查询/公式来评估此问题吗?或者我将不得不使用光标?
由于
答案 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中更容易做,但我想要更多基于集合的操作,所以上面是我提出的,它适用于所有的测试用例。注意我确实在网上添加了一个信用卡。
一般步骤
>=
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()的原因是反向实际上会影响我们正在寻找的相反方向的平衡。结果:
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)
您需要将借记和点数分成单个单位,然后按时间顺序排列,并过滤掉匹配的行,然后您可以按期间对其进行老化。
只需为每个时段调整总和。
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;