如何将Excel公式转换为SQL以计算每日复利

时间:2019-11-26 16:33:43

标签: sql sql-server

我正在尝试将Excel公式转换为SQL以查找每日复利。在excel中,我们使用的公式如下:

Date   Rate    Balance  Accrual     (Formula)
11/19  0.0529  8000     1.159452    (=C2*(B2/365))
11/20  0.0529  8000     1.159620    (=(C3+SUM($D$2:$D2))*(B3/365))
11/21  0.0529  7000     1.014857    (=(C4+SUM($D$2:$D3))*(B4/365))

我需要能够提取每日应计费用,以便我们可以根据汇率变化或余额变化进行预测,因此最终的总计在这种情况下将无法正常工作。

我尝试了一些公式来获得相同的结果,例如:

1. balance*(apr/365)
2. sum((balance * (apr/365))) over (order by date)
3. (balance+sum(balance*(apr/365)) over (order by date))*(apr/365)
  1. 为简单的兴趣而工作,但是我需要在当前行之前保持连续的兴趣
  2. 离我要找的地方不远
  3. 让我非常亲近,但不完全精确

问题是我需要将所有以前的应计金额添加到余额中,然后从中计算出利息。我想念一些东西,但不能完全动弹。 Query也将在SSRS报表中使用,因此SSRS解决方案也能正常工作。

1 个答案:

答案 0 :(得分:3)

这是行之有效的方法。我将逐步介绍发现过程,以帮助可能需要做类似事情的其他人,但是您可以直接跳到下面的Try 3

首先,这是我创建的工作表:

CREATE TABLE #tmp (
    RecDate Date not null Primary Key,    -- Obviously not appropriate PK for real table
    Rate decimal(7,4),
    Balance Decimal(16,4),
    Accrual Decimal(16,6)
    )

INSERT INTO #tmp VALUES 
    ('20191119',0.0529,8000,0),
    ('20191120',0.0529,8000,0),
    ('20191121',0.0529,7000,0)

-尝试1-简单公式

对于那些精通SQL编码的人来说,这将是常见的首次尝试。

UPDATE #tmp
SET Accrual = (
    Balance + ISNULL(
        (
        SELECT SUM(Accrual) 
        FROM #tmp x 
        WHERE x.RecDate < #tmp.RecDate
        ), 0)) * (Rate / 365)

这将适用于计算单利,但不适用于复利,这是结果:

RecDate        Rate    Balance        Accrual
2019-11-19    0.0529    8000.0000    1.159440
2019-11-20    0.0529    8000.0000    1.159440
2019-11-21    0.0529    7000.0000    1.014510

请注意,第1行和第2行的利息相同。这是因为所有行都同时更新,因此在计算下一行之前,不会更新任何行的应计余额。这就是SQL Server设计的工作方式。

同样有趣的是,这将有效 IF ,因为每一行都是在一天结束时计算出来的,因为随后每一行将与具有他们的应计费用已经计算过了。我们可以使用游标来模拟这种形式的数据,一次计算每一行。

-尝试2-光标

这适用于批处理,并且每天都在模拟。

DECLARE cx CURSOR FOR
    SELECT RecDate
    FROM #tmp
    ORDER BY RecDate

DECLARE @Dt Date

OPEN cx
FETCH NEXT FROM cx
    INTO @dt

WHILE @@FETCH_STATUS = 0
BEGIN
    UPDATE #tmp
        SET Accrual = (
            Balance + ISNULL((SELECT SUM(Accrual) FROM #tmp x WHERE x.RecDate < @dt), 0)) 
                * (Rate / 365)
        WHERE CURRENT OF cx

    FETCH NEXT FROM cx
    INTO @dt
END
CLOSE cx
DEALLOCATE cx

结果是:

RecDate       Rate      Balance      Accrual
2019-11-19    0.0529    8000.0000    1.159440
2019-11-20    0.0529    8000.0000    1.159608
2019-11-21    0.0529    7000.0000    1.014846

这将正确累积数据,并添加先前的应计费用。但是,您会注意到,即使计算正确,此输出也不与提供的样本匹配。为什么?

-尝试3-转换RATE值以在计算中允许足够的小数位

在Excel中,数值是高精度浮点,而我的示例数据库正在使用decimal数据类型。为什么?十进制数据类型可以防止计算中的舍入错误。

但是,尽管decimal数据类型可以防止舍入错误,但它们也具有固定的精度长度。计算小数数据类型的公式将以等于大约(2 * [计算中所有值的总小数位])的小数点位数截断结果。因此,当将每日利率计算为(Rate [4 decimal places] / 365 (0 decimal places))时,对于提供的利率(0.0529),结果为0.00014493(2 * 4 = 8个小数位)。

但是,在这种情况下,计算需要比此精度更高的返回正确值。因此,这是Cursor选项的另一个版本:

DECLARE cx CURSOR FOR
    SELECT RecDate
    FROM #tmp
    ORDER BY RecDate

DECLARE @Dt Date

OPEN cx
FETCH NEXT FROM cx
    INTO @dt

WHILE @@FETCH_STATUS = 0
BEGIN
    UPDATE #tmp
        SET Accrual = (
            Balance + ISNULL((SELECT SUM(Accrual) FROM #tmp x WHERE x.RecDate < @dt), 0))
                * (CAST(Rate as decimal(38,35)) / 365)   --<<-- CHANGED HERE
        WHERE CURRENT OF cx

    FETCH NEXT FROM cx
    INTO @dt
END
CLOSE cx
DEALLOCATE cx

使用此高精度版本,我们的结果与用户的结果相符:

RecDate       Rate      Balance      Accrual
2019-11-19    0.0529    8000.0000    1.159452
2019-11-20    0.0529    8000.0000    1.159620
2019-11-21    0.0529    7000.0000    1.014857

希望这已经足够彻底地解决了这个问题。