使用非 CLR 产品聚合进行更新会导致差一

UPDATE with non-CLR product aggregate results in off-by-one

我在回答另一个问题,运行 进入 st运行ge 结果 - 产品聚合(没有 CLR)的输出在 SELECT 与更新中使用时不同.

这是对原始问题的简化,以最大限度地重现问题:

GroupKey    RowIndex    A
----------- ----------- -----------
25          1           5
25          2           6
25          3           NULL
26          1           3
26          2           4
26          3           NULL

每个组键的目标是将每行的 A 列更新为 RowIndex = 3 到每行的 A 列的乘积 RowIndex IN (1, 2),所以这会产生以下变化:

GroupKey    RowIndex    A
----------- ----------- -----------
25          3           30
26          3           12

所以这是我使用的代码:

UPDATE T SET
    A = Products.Product
FROM @Table T
    INNER JOIN (
        SELECT
            GroupKey,
            EXP(SUM(LOG(A))) AS Product
        FROM @Table
        WHERE RowIndex IN (1, 2)
        GROUP BY
            GroupKey
    ) Products
        ON Products.GroupKey = T.GroupKey
WHERE T.RowIndex = 3
SELECT * FROM @Table WHERE RowIndex = 3

然后产生差一结果:

GroupKey    RowIndex    A
----------- ----------- -----------
25          3           29
26          3           12

如果我只是 运行 子查询,我会看到正确的值。

GroupKey    Product
----------- ----------------------
25          30
26          12

这是完整的脚本,可以轻松使用。我不知道差一个是从哪里来的。

DECLARE @Table TABLE (GroupKey INT, RowIndex INT, A INT)
INSERT @Table VALUES (25, 1, 5), (25, 2, 6), (25, 3, NULL), (26, 1, 3), (26, 2, 4), (26, 3, NULL)

SELECT * FROM @Table

SELECT
    GroupKey,
    EXP(SUM(LOG(A))) AS Product
FROM @Table
WHERE RowIndex IN (1, 2)
GROUP BY
    GroupKey

UPDATE T SET
    A = Products.Product
FROM @Table T
    INNER JOIN (
        SELECT
            GroupKey,
            EXP(SUM(LOG(A))) AS Product
        FROM @Table
        WHERE RowIndex IN (1, 2)
        GROUP BY
            GroupKey
    ) Products
        ON Products.GroupKey = T.GroupKey
WHERE T.RowIndex = 3
SELECT * FROM @Table WHERE RowIndex = 3

以下是我遇到的一些参考资料:

我想说,如果您想使用 ints,这个可爱的 "PRODUCT" 聚合本质上是不可靠的 - EXP and LOG 仅针对 float 类型定义所以我们得到了舍入误差。

为什么他们没有始终出现,我不能说,只是暗示不同的查询可能会导致评估顺序发生变化。

作为一个更简单的例子来说明这可能会出错:

select CAST(EXP(LOG(5)) as int)

可以生产4EXPLOG 一起将产生 小于 5 的值,但当然在转换为 int 时,SQL 服务器总是截断而不是应用任何舍入。