跨多个利率计算利息

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_begininter_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