跨多个利率计算利息
Calculating interest across multiple interest rates
我有一个 table 存储利率的地方,每个利率都有一个适用的开始日期。 table 中较晚的条目取代较早的条目。我必须使用开始日期、结束日期和金额来查询此 table。根据这些值,我需要得出一个总利息金额,该金额考虑了日期跨度的不同利率。
CREATE TABLE [dbo].[Interest_Rates](
[Interest_Rate] [float] NULL,
[Incept_Date] [datetime] NULL
) ON [PRIMARY]
GO
我有四个'bands'的利率:
INSERT [dbo].[Interest_Rates] ([Interest_Rate], [Incept_Date]) VALUES (10, CAST(N'2001-05-03 11:12:16.000' AS DateTime))
GO
INSERT [dbo].[Interest_Rates] ([Interest_Rate], [Incept_Date]) VALUES (11.5, CAST(N'2014-01-07 10:49:28.433' AS DateTime))
GO
INSERT [dbo].[Interest_Rates] ([Interest_Rate], [Incept_Date]) VALUES (13.5, CAST(N'2016-03-01 00:00:00.000' AS DateTime))
GO
INSERT [dbo].[Interest_Rates] ([Interest_Rate], [Incept_Date]) VALUES (15.5, CAST(N'2016-05-01 00:00:00.000' AS DateTime))
GO
我想知道的是,是否有可能计算从利率为 11.5% 的时间开始到利率为 11.5% 的稍后时间结束的一段时间的利率在一次查询中,比率上升了两倍,达到 13.5%。
似乎每个 'band' 的利息计算都可以使用精彩的 Suprotim Agarwal 示例完成,如下所示:
DECLARE @StartDate DateTime
DECLARE @EndDate DateTime
DECLARE @Amount Float
SET @StartDate = '2014-04-22'
SET @EndDate = '2016-04-13'
SET @Amount = 150000.00
SELECT
@Amount*(POWER(1.1550, CONVERT(NUMERIC(8,3),
DATEDIFF(d, @StartDate, @EndDate)/365.25))) - @Amount
as TotalInterest
(上例中利率为 15.5%)
我遇到困难的地方在于如何将计算与利率 table 相互关联,以便连接考虑到 'band' 日期跨度的每个小节属于哪个部分.
如有任何帮助或建议,我们将不胜感激。
tl;dr:完整的查询是这个长解释结束时的最后一个代码块。
让我们逐步了解这一过程,然后将最终解决方案作为一个查询提出。需要几个步骤来解决这个问题。
1) 找出我们想要的日期范围涵盖的费率
2) 设计一种巧妙的方式来选择这些比率
3) 将这些日期和利率结合起来,得出总计利息。
一些初步笔记
由于您的利率计算示例将天数作为其最佳分辨率,我只使用数据类型 date 而不是 datetime。如果您需要更精细的分辨率,请告诉我,我可以更新。
我正在使用以下声明的变量
declare @EndOfTime date = '2049-12-31' -- This is some arbitrary end of time value that I chose
declare @StartDate Date = '2012-04-22' -- I made this earlier to cover more rates
declare @EndDate Date = '2016-04-13'
declare @Amount Float = 100000.00 -- I changed it to a softer number
1) 日期间隔
现在,您的 interest_rates table 列出了这样的日期:
+ ------------- + ----------- +
| interest_rate | incept_date |
+ ------------- + ----------- +
| 10 | 2001-05-03 |
| 11.5 | 2014-01-07 |
| 13.5 | 2016-03-01 |
| 15.5 | 2016-05-01 |
+ ------------- + ----------- +
但是您希望它像这样列出间隔:
+ ------------- + ------------ + ------------ +
| interest_rate | inter_begin | inter_end |
+ ------------- + ------------ + ------------ +
| 10 | 2001-05-03 | 2014-01-06 |
| 11.5 | 2014-01-07 | 2016-02-29 |
| 13.5 | 2016-03-01 | 2016-04-30 |
| 15.5 | 2016-05-01 | 2049-12-31 |
+ ------------- + ------------ + ------------ +
以下查询可以将您的日期列表转换为间隔:
select i1.interest_rate
, i1.incept_date as inter_begin
, isnull(min(i2.incept_date) - 1,@EndOfTime) as inter_end
from #interest i1
left join #interest i2 on i2.incept_date > i1.incept_date
group by i1.interest_rate, i1.incept_date
注意:我在这里的日期算法有点松散,没有使用 dateadd() 命令。
像这样跟踪日期间隔使select适用的费率更容易。
2) 选择费率
现在我们可以 select 通过将上述查询用作 CTE,我们希望范围内的记录。这个查询有点棘手,所以花点时间真正理解它。
; with
intervals as (
-- The above query/table
)
select *
from intervals
where inter_begin >= (
select inter_begin -- selects the first rate covered by our desired interval
from intervals
where @StartDate between inter_begin and inter_end
)
and inter_end <= (
select inter_end -- selects the last rate covered by our desired interval
from intervals
where @EndDate between inter_begin and inter_end
)
这有效地过滤掉了我们不关心的任何费率并留给我们
+ ------------- + ------------ + ------------ +
| interest_rate | inter_begin | inter_end |
+ ------------- + ------------ + ------------ +
| 10 | 2001-05-03 | 2014-01-06 |
| 11.5 | 2014-01-07 | 2016-02-29 |
| 13.5 | 2016-03-01 | 2016-04-30 |
+ ------------- + ------------ + ------------ +
3) 计算利息
现在我们什么都有了,计算利息只是select从这个table中取对的事情。您为计算所写的大部分内容保持不变;主要更改在 datediff() 命令中。使用 @StartDate 和 @EndDate 不会为我们提供以每个特定比率花费的天数的准确计数。我们 运行 通过使用 inter_begin 和 inter_end 解决了同样的问题。相反,我们必须使用 case 语句,例如
datediff(day,
case when @StartDate > inter_begin then @StartDate else inter_begin end,
case when @EndDate < inter_end then @EndDate else inter_end end
)
把这个放在上面的Query中得到
; with
intervals as (...) -- same as above
select *
, DATEDIFF(day,
case when @StartDate > inter_begin then @StartDate else inter_begin end,
case when @EndDate < inter_end then @EndDate else inter_end end) as days_active
, @Amount*(POWER((1+interest_rate/100),
convert(float,
DATEDIFF(day,
case when @StartDate > inter_begin then @StartDate else inter_begin end,
case when @EndDate < inter_end then @EndDate else inter_end end
)
)/365.25)
) - @Amount as Actual_Interest
from ... -- same as above
这给了我们这个 table
+ ------------- + ------------ + ------------ + ----------- + --------------- +
| interest_rate | inter_begin | inter_end | days_active | Actual_interest |
+ ------------- + ------------ + ------------ + ----------- + --------------- +
| 10 | 2001-05-03 | 2014-01-06 | 624 | 17683.63 |
| 11.5 | 2014-01-07 | 2016-02-29 | 786 | 26283.00 |
| 13.5 | 2016-03-01 | 2016-04-30 | 43 | 1501.98 |
+ ------------- + ------------ + ------------ + ----------- + --------------- +
最后,将其放入 CTE 中并取 Actual_interest 字段的总和:
declare @EndOfTime date = '2049-12-31' -- This is some arbitrary end of time value that I chose
declare @StartDate Date = '2012-04-22' -- I made this earlier to cover more rates
declare @EndDate Date = '2016-04-13'
declare @Amount Float = 100000.00 -- I changed it to a softer number
; with
intervals as (
select i1.interest_rate
, i1.incept_date as inter_begin
, isnull(min(i2.incept_date) - 1,@EndOfTime) as inter_end
from #interest i1
left join #interest i2 on i2.incept_date > i1.incept_date
group by i1.interest_rate, i1.incept_date
)
, interest as (
select *
, DATEDIFF(day,
case when @StartDate > inter_begin then @StartDate else inter_begin end,
case when @EndDate < inter_end then @EndDate else inter_end end) as days_active
, @Amount*(POWER((1+interest_rate/100),
convert(float,
DATEDIFF(day,
case when @StartDate > inter_begin then @StartDate else inter_begin end,
case when @EndDate < inter_end then @EndDate else inter_end end
)
)/365.25)
) - @Amount as Actual_Interest
from intervals
where inter_begin >= (
select inter_begin -- selects the first rate covered by our desired interval
from intervals
where @StartDate between inter_begin and inter_end
)
and inter_end <= (
select inter_end -- selects the last rate covered by our desired interval
from intervals
where @EndDate between inter_begin and inter_end
)
)
select sum(actual_interest) as total_interest
from interest
可能比您要找的多一点,但在这个例子中,您可以在一个查询中计算所有贷款。
您可能还会注意到最后 3 列代表总天数、总利息收入和总加权平均利率
例子
Declare @Interest_Rate table (interest_rate money,Incept_Date datetime)
Insert Into @Interest_Rate values
(10 ,'2001-05-03 11:12:16.000'),
(11.5,'2014-01-07 10:49:28.433'),
(13.5,'2016-03-01 00:00:00.000'),
(15.5,'2016-05-01 00:00:00.000')
Declare @Loan table (Id int,StartDate date, EndDate date,Amount money)
Insert Into @Loan values
(1,'2014-01-01','2015-11-17',150000),
(1,'2015-11-18','2016-12-31',175000), -- Notice Balance Change
(2,'2016-01-01','2020-06-15',200000)
Select A.ID
,A.Amount
,DateR1 = min(D)
,DateR2 = max(D)
,Days = count(*)
,B.Interest_Rate
,Interest_Earned = cast(sum(((A.Amount*B.Interest_Rate)/B.DIY)/100.0) as decimal(18,2))
,Total_Days = sum(count(*)) over (Partition By A.ID)
,Total_Int_Earned = sum(cast(sum(((A.Amount*B.Interest_Rate)/B.DIY)/100.0) as decimal(18,2))) over (Partition By A.ID)
,Total_WAIR = sum(A.Amount * count(*) * B.interest_rate) over (Partition By A.ID)/ sum(A.Amount * count(*)) over (Partition By A.ID)
From @Loan A
Join (
Select D
,D1
,interest_rate
,DIY = 365.0 + IIF(Year(D) % 4 = 0 , 1 , 0 )
From ( Select Top (DateDiff(DD,(Select cast(min(Incept_Date) as date) from @Interest_Rate),cast(GetDate() as date))+1) D=DateAdd(DD,-1+Row_Number() Over (Order By (Select NULL)),(Select cast(min(Incept_Date) as date) from @Interest_Rate)) From master..spt_values N1,master..spt_values N2 ) A
Join (
Select interest_rate
,D1 = cast(Incept_Date as Date)
,D2 = cast(DateAdd(DAY,-1,Lead(Incept_Date,1,GetDate()) over (Order by Incept_Date)) as date)
From @Interest_Rate
) B on D between D1 and D2
) B on D Between StartDate and EndDate
Group By A.ID,A.Amount,B.D1,B.Interest_Rate
Returns
我有一个 table 存储利率的地方,每个利率都有一个适用的开始日期。 table 中较晚的条目取代较早的条目。我必须使用开始日期、结束日期和金额来查询此 table。根据这些值,我需要得出一个总利息金额,该金额考虑了日期跨度的不同利率。
CREATE TABLE [dbo].[Interest_Rates](
[Interest_Rate] [float] NULL,
[Incept_Date] [datetime] NULL
) ON [PRIMARY]
GO
我有四个'bands'的利率:
INSERT [dbo].[Interest_Rates] ([Interest_Rate], [Incept_Date]) VALUES (10, CAST(N'2001-05-03 11:12:16.000' AS DateTime))
GO
INSERT [dbo].[Interest_Rates] ([Interest_Rate], [Incept_Date]) VALUES (11.5, CAST(N'2014-01-07 10:49:28.433' AS DateTime))
GO
INSERT [dbo].[Interest_Rates] ([Interest_Rate], [Incept_Date]) VALUES (13.5, CAST(N'2016-03-01 00:00:00.000' AS DateTime))
GO
INSERT [dbo].[Interest_Rates] ([Interest_Rate], [Incept_Date]) VALUES (15.5, CAST(N'2016-05-01 00:00:00.000' AS DateTime))
GO
我想知道的是,是否有可能计算从利率为 11.5% 的时间开始到利率为 11.5% 的稍后时间结束的一段时间的利率在一次查询中,比率上升了两倍,达到 13.5%。
似乎每个 'band' 的利息计算都可以使用精彩的 Suprotim Agarwal 示例完成,如下所示:
DECLARE @StartDate DateTime
DECLARE @EndDate DateTime
DECLARE @Amount Float
SET @StartDate = '2014-04-22'
SET @EndDate = '2016-04-13'
SET @Amount = 150000.00
SELECT
@Amount*(POWER(1.1550, CONVERT(NUMERIC(8,3),
DATEDIFF(d, @StartDate, @EndDate)/365.25))) - @Amount
as TotalInterest
(上例中利率为 15.5%)
我遇到困难的地方在于如何将计算与利率 table 相互关联,以便连接考虑到 'band' 日期跨度的每个小节属于哪个部分.
如有任何帮助或建议,我们将不胜感激。
tl;dr:完整的查询是这个长解释结束时的最后一个代码块。
让我们逐步了解这一过程,然后将最终解决方案作为一个查询提出。需要几个步骤来解决这个问题。
1) 找出我们想要的日期范围涵盖的费率
2) 设计一种巧妙的方式来选择这些比率
3) 将这些日期和利率结合起来,得出总计利息。
一些初步笔记
由于您的利率计算示例将天数作为其最佳分辨率,我只使用数据类型 date 而不是 datetime。如果您需要更精细的分辨率,请告诉我,我可以更新。
我正在使用以下声明的变量
declare @EndOfTime date = '2049-12-31' -- This is some arbitrary end of time value that I chose
declare @StartDate Date = '2012-04-22' -- I made this earlier to cover more rates
declare @EndDate Date = '2016-04-13'
declare @Amount Float = 100000.00 -- I changed it to a softer number
1) 日期间隔
现在,您的 interest_rates table 列出了这样的日期:
+ ------------- + ----------- +
| interest_rate | incept_date |
+ ------------- + ----------- +
| 10 | 2001-05-03 |
| 11.5 | 2014-01-07 |
| 13.5 | 2016-03-01 |
| 15.5 | 2016-05-01 |
+ ------------- + ----------- +
但是您希望它像这样列出间隔:
+ ------------- + ------------ + ------------ +
| interest_rate | inter_begin | inter_end |
+ ------------- + ------------ + ------------ +
| 10 | 2001-05-03 | 2014-01-06 |
| 11.5 | 2014-01-07 | 2016-02-29 |
| 13.5 | 2016-03-01 | 2016-04-30 |
| 15.5 | 2016-05-01 | 2049-12-31 |
+ ------------- + ------------ + ------------ +
以下查询可以将您的日期列表转换为间隔:
select i1.interest_rate
, i1.incept_date as inter_begin
, isnull(min(i2.incept_date) - 1,@EndOfTime) as inter_end
from #interest i1
left join #interest i2 on i2.incept_date > i1.incept_date
group by i1.interest_rate, i1.incept_date
注意:我在这里的日期算法有点松散,没有使用 dateadd() 命令。
像这样跟踪日期间隔使select适用的费率更容易。
2) 选择费率
现在我们可以 select 通过将上述查询用作 CTE,我们希望范围内的记录。这个查询有点棘手,所以花点时间真正理解它。
; with
intervals as (
-- The above query/table
)
select *
from intervals
where inter_begin >= (
select inter_begin -- selects the first rate covered by our desired interval
from intervals
where @StartDate between inter_begin and inter_end
)
and inter_end <= (
select inter_end -- selects the last rate covered by our desired interval
from intervals
where @EndDate between inter_begin and inter_end
)
这有效地过滤掉了我们不关心的任何费率并留给我们
+ ------------- + ------------ + ------------ +
| interest_rate | inter_begin | inter_end |
+ ------------- + ------------ + ------------ +
| 10 | 2001-05-03 | 2014-01-06 |
| 11.5 | 2014-01-07 | 2016-02-29 |
| 13.5 | 2016-03-01 | 2016-04-30 |
+ ------------- + ------------ + ------------ +
3) 计算利息
现在我们什么都有了,计算利息只是select从这个table中取对的事情。您为计算所写的大部分内容保持不变;主要更改在 datediff() 命令中。使用 @StartDate 和 @EndDate 不会为我们提供以每个特定比率花费的天数的准确计数。我们 运行 通过使用 inter_begin 和 inter_end 解决了同样的问题。相反,我们必须使用 case 语句,例如
datediff(day,
case when @StartDate > inter_begin then @StartDate else inter_begin end,
case when @EndDate < inter_end then @EndDate else inter_end end
)
把这个放在上面的Query中得到
; with
intervals as (...) -- same as above
select *
, DATEDIFF(day,
case when @StartDate > inter_begin then @StartDate else inter_begin end,
case when @EndDate < inter_end then @EndDate else inter_end end) as days_active
, @Amount*(POWER((1+interest_rate/100),
convert(float,
DATEDIFF(day,
case when @StartDate > inter_begin then @StartDate else inter_begin end,
case when @EndDate < inter_end then @EndDate else inter_end end
)
)/365.25)
) - @Amount as Actual_Interest
from ... -- same as above
这给了我们这个 table
+ ------------- + ------------ + ------------ + ----------- + --------------- +
| interest_rate | inter_begin | inter_end | days_active | Actual_interest |
+ ------------- + ------------ + ------------ + ----------- + --------------- +
| 10 | 2001-05-03 | 2014-01-06 | 624 | 17683.63 |
| 11.5 | 2014-01-07 | 2016-02-29 | 786 | 26283.00 |
| 13.5 | 2016-03-01 | 2016-04-30 | 43 | 1501.98 |
+ ------------- + ------------ + ------------ + ----------- + --------------- +
最后,将其放入 CTE 中并取 Actual_interest 字段的总和:
declare @EndOfTime date = '2049-12-31' -- This is some arbitrary end of time value that I chose
declare @StartDate Date = '2012-04-22' -- I made this earlier to cover more rates
declare @EndDate Date = '2016-04-13'
declare @Amount Float = 100000.00 -- I changed it to a softer number
; with
intervals as (
select i1.interest_rate
, i1.incept_date as inter_begin
, isnull(min(i2.incept_date) - 1,@EndOfTime) as inter_end
from #interest i1
left join #interest i2 on i2.incept_date > i1.incept_date
group by i1.interest_rate, i1.incept_date
)
, interest as (
select *
, DATEDIFF(day,
case when @StartDate > inter_begin then @StartDate else inter_begin end,
case when @EndDate < inter_end then @EndDate else inter_end end) as days_active
, @Amount*(POWER((1+interest_rate/100),
convert(float,
DATEDIFF(day,
case when @StartDate > inter_begin then @StartDate else inter_begin end,
case when @EndDate < inter_end then @EndDate else inter_end end
)
)/365.25)
) - @Amount as Actual_Interest
from intervals
where inter_begin >= (
select inter_begin -- selects the first rate covered by our desired interval
from intervals
where @StartDate between inter_begin and inter_end
)
and inter_end <= (
select inter_end -- selects the last rate covered by our desired interval
from intervals
where @EndDate between inter_begin and inter_end
)
)
select sum(actual_interest) as total_interest
from interest
可能比您要找的多一点,但在这个例子中,您可以在一个查询中计算所有贷款。
您可能还会注意到最后 3 列代表总天数、总利息收入和总加权平均利率
例子
Declare @Interest_Rate table (interest_rate money,Incept_Date datetime)
Insert Into @Interest_Rate values
(10 ,'2001-05-03 11:12:16.000'),
(11.5,'2014-01-07 10:49:28.433'),
(13.5,'2016-03-01 00:00:00.000'),
(15.5,'2016-05-01 00:00:00.000')
Declare @Loan table (Id int,StartDate date, EndDate date,Amount money)
Insert Into @Loan values
(1,'2014-01-01','2015-11-17',150000),
(1,'2015-11-18','2016-12-31',175000), -- Notice Balance Change
(2,'2016-01-01','2020-06-15',200000)
Select A.ID
,A.Amount
,DateR1 = min(D)
,DateR2 = max(D)
,Days = count(*)
,B.Interest_Rate
,Interest_Earned = cast(sum(((A.Amount*B.Interest_Rate)/B.DIY)/100.0) as decimal(18,2))
,Total_Days = sum(count(*)) over (Partition By A.ID)
,Total_Int_Earned = sum(cast(sum(((A.Amount*B.Interest_Rate)/B.DIY)/100.0) as decimal(18,2))) over (Partition By A.ID)
,Total_WAIR = sum(A.Amount * count(*) * B.interest_rate) over (Partition By A.ID)/ sum(A.Amount * count(*)) over (Partition By A.ID)
From @Loan A
Join (
Select D
,D1
,interest_rate
,DIY = 365.0 + IIF(Year(D) % 4 = 0 , 1 , 0 )
From ( Select Top (DateDiff(DD,(Select cast(min(Incept_Date) as date) from @Interest_Rate),cast(GetDate() as date))+1) D=DateAdd(DD,-1+Row_Number() Over (Order By (Select NULL)),(Select cast(min(Incept_Date) as date) from @Interest_Rate)) From master..spt_values N1,master..spt_values N2 ) A
Join (
Select interest_rate
,D1 = cast(Incept_Date as Date)
,D2 = cast(DateAdd(DAY,-1,Lead(Incept_Date,1,GetDate()) over (Order by Incept_Date)) as date)
From @Interest_Rate
) B on D between D1 and D2
) B on D Between StartDate and EndDate
Group By A.ID,A.Amount,B.D1,B.Interest_Rate
Returns