【发布时间】:2016-02-29 13:32:27
【问题描述】:
我正在尝试执行累积乘法。我正在尝试两种方法来做到这一点
样本数据:
DECLARE @TEST TABLE
(
PAR_COLUMN INT,
PERIOD INT,
VALUE NUMERIC(22, 6)
)
INSERT INTO @TEST VALUES
(1,601,10 ),
(1,602,20 ),
(1,603,30 ),
(1,604,40 ),
(1,605,50 ),
(1,606,60 ),
(2,601,100),
(2,602,200),
(2,603,300),
(2,604,400),
(2,605,500),
(2,606,600)
注意:value 列中的数据永远不会是整数,值会有小数部分。为了显示近似问题,我将示例值保留为整数。
方法一:EXP + LOG + SUM() Over(Order by)
在这种方法中,我使用EXP + LOG + SUM() Over(Order by) 技术来查找累积乘法。在这种方法中,数值不准确;结果中存在一些舍入和近似问题。
SELECT *,
Exp(Sum(Log(Abs(NULLIF(VALUE, 0))))
OVER(
PARTITION BY PAR_COLUMN
ORDER BY PERIOD)) AS CUM_MUL
FROM @TEST;
结果:
PAR_COLUMN PERIOD VALUE CUM_MUL
---------- ------ --------- ----------------
1 601 10.000000 10
1 602 20.000000 200 -- 10 * 20 = 200(correct)
1 603 30.000000 6000.00000000001 -- 200 * 30 = 6000.000000000 (not 6000.00000000001) incorrect
1 604 40.000000 240000
1 605 50.000000 12000000
1 606 60.000000 720000000.000001 -- 12000000 * 60 = 720000000.000000 (not 720000000.000001) incorrect
2 601 100.000000 100
2 602 200.000000 20000
2 603 300.000000 5999999.99999999 -- 20000.000000 *300.000000 = 6000000.000000 (not 5999999.99999999) incorrect
2 604 400.000000 2399999999.99999
2 605 500.000000 1199999999999.99
2 606 600.000000 719999999999998
方法2:传统乘法(递归CTE)
此方法完美运行,没有任何舍入或近似问题。
;WITH CTE
AS (SELECT TOP 1 WITH TIES PAR_COLUMN,
PERIOD,
VALUE,
CUM_MUL = VALUE
FROM @TEST
ORDER BY PERIOD
UNION ALL
SELECT T.PAR_COLUMN,
T.PERIOD,
T.VALUE,
Cast(T.VALUE * C.CUM_MUL AS NUMERIC(22, 6))
FROM CTE C
INNER JOIN @TEST T
ON C.PAR_COLUMN = T.PAR_COLUMN
AND T.PERIOD = C.PERIOD + 1)
SELECT *
FROM CTE
ORDER BY PAR_COLUMN,PERIOD
结果
PAR_COLUMN PERIOD VALUE CUM_MUL
---------- ------ --------- ----------------
1 601 10.000000 10.000000
1 602 20.000000 200.000000
1 603 30.000000 6000.000000
1 604 40.000000 240000.000000
1 605 50.000000 12000000.000000
1 606 60.000000 720000000.000000
2 601 100.000000 100.000000
2 602 200.000000 20000.000000
2 603 300.000000 6000000.000000
2 604 400.000000 2400000000.000000
2 605 500.000000 1200000000000.000000
2 606 600.000000 720000000000000.000000
谁能告诉我为什么方法 1 中的值不准确以及如何解决?我尝试将数据类型更改为Float,并在numeric 中增加scale,但没有用。
我很想用方法一,比方法二快很多。
编辑:现在我知道了近似的原因。任何人都可以找到解决此问题的方法吗?
【问题讨论】:
-
原因很简单。精度是有限的,所以
SELECT EXP (LOG (1000))不会返回 1000 -
是否有任何解决方法
-
@lad2025 - 实际上它返回
1000。但是EXP (LOG (99787.564))返回99787.5639999999 -
我会说这取决于检查demo
-
您有什么理由不使用 CLR 聚合?喜欢这里stackoverflow.com/a/30757189/73226
标签: sql sql-server sql-server-2008 sql-server-2012