我正在尝试将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)
问题是我需要将所有以前的应计金额添加到余额中,然后从中计算出利息。我想念一些东西,但不能完全动弹。 Query也将在SSRS报表中使用,因此SSRS解决方案也能正常工作。
答案 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
希望这已经足够彻底地解决了这个问题。