BINARY_CHECKSUM-不同的结果取决于行数

时间:2018-12-31 15:22:32

标签: sql sql-server tsql

我想知道为什么BINARY_CHECKSUM函数对于相同的函数返回不同的结果:

SELECT *, BINARY_CHECKSUM(a,b) AS bc
FROM (VALUES(1, NULL, 100),
            (2, NULL, NULL),
            (3, 1, 2)) s(id,a,b);

SELECT *, BINARY_CHECKSUM(a,b) AS bc
FROM (VALUES(1, NULL, 100),
            (2, NULL, NULL)) s(id,a,b);

输出:

+-----+----+------+-------------+
| id  | a  |  b   |     bc      |
+-----+----+------+-------------+
|  1  |    | 100  |        -109 |
|  2  |    |      | -2147483640 |
|  3  | 1  |   2  |          18 |
+-----+----+------+-------------+

-- -109 vs 100
+-----+----+------+------------+
| id  | a  |  b   |     bc     |
+-----+----+------+------------+
|  1  |    | 100  |        100 |
|  2  |    |      | 2147483647 |
+-----+----+------+------------+

对于第二个样本,我得到了我所期望的:

SELECT *, BINARY_CHECKSUM(a,b) AS bc
FROM (VALUES(1, 1, 100),
            (2, 3, 4),
            (3,1,1)) s(id,a,b);

SELECT *, BINARY_CHECKSUM(a,b) AS bc
FROM (VALUES(1, 1, 100),
            (2, 3, 4)) s(id,a,b);

前两行的输入:

+-----+----+------+-----+
| id  | a  |  b   | bc  |
+-----+----+------+-----+
|  1  | 1  | 100  | 116 |
|  2  | 3  |   4  |  52 |
+-----+----+------+-----+

db<>fiddle demo


当我想比较两个表/查询时会产生奇怪的结果:

WITH t AS (
  SELECT 1 AS id, NULL AS a, 100 b
  UNION ALL SELECT 2, NULL, NULL
  UNION ALL SELECT 3, 1, 2           -- comment this out
), s AS (
  SELECT 1 AS id ,100 AS a, NULL as b
  UNION ALL SELECT 2, NULL, NULL
  UNION ALL SELECT 3, 2, 1           -- comment this out
)
SELECT t.*,s.*
  ,BINARY_CHECKSUM(t.a, t.b) AS bc_t, BINARY_CHECKSUM(s.a, s.b) AS bc_s
FROM t
JOIN s
  ON s.id = t.id
WHERE BINARY_CHECKSUM(t.a, t.b) = BINARY_CHECKSUM(s.a, s.b);

db<>fiddle demo2

对于3行,我得到单个结果:

+-----+----+----+-----+----+----+--------------+-------------+
| id  | a  | b  | id  | a  | b  |    bc_t      |    bc_s     |
+-----+----+----+-----+----+----+--------------+-------------+
|  2  |    |    |  2  |    |    | -2147483640  | -2147483640 |
+-----+----+----+-----+----+----+--------------+-------------+

但是对于2行,我也得到id = 1:

+-----+----+------+-----+------+----+-------------+------------+
| id  | a  |  b   | id  |  a   | b  |    bc_t     |    bc_s    |
+-----+----+------+-----+------+----+-------------+------------+
|  1  |    | 100  |  1  | 100  |    |        100  |        100 |
|  2  |    |      |  2  |      |    | 2147483647  | 2147483647 |
+-----+----+------+-----+------+----+-------------+------------+

备注:

  • 我没有在搜索(HASH_BYTES / MD5 / CHECKSUM)之类的替代方案

  • 我知道BINARY_CHECKSUM可能会导致冲突(两个不同的调用会产生相同的输出),这里的场景有些不同

  

对于这个定义,我们说指定类型的空值,   比较相等的值。如果至少有一个值   表达式列表更改时,表达式校验和也可以更改。   但是,这不能保证。因此,要检测值是否   已更改,我们建议仅在以下情况下使用BINARY_CHECKSUM:   应用程序可以容忍偶尔的更改。

哈希函数对于相同的输入参数返回不同的结果,这对我来说很奇怪。 这是设计使然,还是某种故障?

编辑:

@scsimon  指出它适用于物化表,但不适用于cte。 db<>fiddle actual table

cte的元数据:

SELECT name, system_type_name
FROM sys.dm_exec_describe_first_result_set('
SELECT *
FROM (VALUES(1, NULL, 100),
            (2, NULL, NULL),
            (3, 1, 2)) s(id,a,b)', NULL,0);

SELECT name, system_type_name
FROM sys.dm_exec_describe_first_result_set('
SELECT *
FROM (VALUES(1, NULL, 100),
            (2, NULL, NULL)) s(id,a,b)', NULL,0)

-- working workaround
SELECT name, system_type_name
FROM sys.dm_exec_describe_first_result_set('
SELECT *
FROM (VALUES(1, cast(NULL as int), 100),
            (2, NULL, NULL)) s(id,a,b)', NULL,0)

在所有情况下,所有列均为INT,但使用显式CAST时,其行为应符合预期。

db<>fidde metadata

2 个答案:

答案 0 :(得分:4)

这与行数无关。这是因为2行版本的一列中的值始终为NULLNULL的默认类型为int,数字常量(此长度)的默认类型为int,因此它们应该是可比较的。但是从values()派生表来看,这些类型(显然)不是完全相同的类型。

尤其是,派生表中仅包含无类型NULL的列是不可比较的,因此将其从二进制校验和计算中排除。在真实表中不会发生这种情况,因为所有列都有类型。

其余的答案说明了正在发生的事情。

代码在类型转换中的行为符合预期:

SELECT *, BINARY_CHECKSUM(a, b) AS bc
FROM (VALUES(1, cast(NULL as int), 100),
            (2, NULL, NULL)
     ) s(id,a,b);

Here是db <>小提琴。

实际上,使用值创建表表明仅具有NULL值的列与具有显式数字的列具有完全相同的类型。这表明原始代码应该可以工作。但是,显式强制转换也可以解决此问题。很奇怪。

这真的,真的很奇怪。请考虑以下内容:

select v.*, checksum(a, b), checksum(c,b)
FROM (VALUES(1, NULL, 100, NULL),
            (2, 1, 2, 1.0)
     ) v(id, a, b, c);

“ d”类型的更改会影响第二行的binary_checksum(),但不会影响第一行。

这是我的结论。当列中的所有值均为二进制时,binary_checksum()会意识到这一点,并且该列属于“不可比数据类型”类别。然后,校验和基于剩余的列。

您可以通过在运行时看到错误来验证这一点:

select v.*, binary_checksum(a)
FROM (VALUES(1, NULL, 100, NULL),
            (2, NULL,    2,   1.0)
     ) v(    id,a,    b,   c);

它抱怨:

  

参数数据类型NULL对校验和函数的参数1无效。

具有讽刺意味的是,如果将结果保存到表中并使用binary_checksum(),这不是 问题。问题似乎是与values()和数据类型之间存在某种交互作用,但在information_schema.columns表中并没有立即显现出来。

喜人的消息是,即使在values()生成的派生表上不起作用,该代码也应该在表上工作-正如this SQL Fiddle演示的那样。

我还了解到,用NULL填充的列实际上是无类型的。定义表时似乎在int中分配select into数据类型。 “无类型”类型将转换为int

答案 1 :(得分:4)

对于没有NULL(且列中没有任何类型的值)的文字CAST,它会完全忽略它,而只给出与BINARY_CHECKSUM(b)相同的结果。

这似乎很早就发生了。

的初始树表示输出
SELECT *, BINARY_CHECKSUM(a,b) AS bc
FROM (VALUES(1, NULL, 100),
            (2, NULL, NULL)) s(id,a,b)
OPTION (RECOMPILE, QUERYTRACEON 8605, QUERYTRACEON 3604);

已经表明它已经决定只使用一列作为函数的输入

ScaOp_Intrinsic binary_checksum

    ScaOp_Identifier COL: Union1008 

与您的第一个查询的以下输出进行比较

ScaOp_Intrinsic binary_checksum

    ScaOp_Identifier COL: Union1011 

    ScaOp_Identifier COL: Union1010 

如果您尝试通过{p>来获得BINARY_CHECKSUM

SELECT *, BINARY_CHECKSUM(a) AS bc
FROM (VALUES(1, NULL, 100)) s(id,a,b)

出现错误

  

消息8184,级别16,状态1,行8二进制校验和错误。有   binarychecksum输入中没有可比较的列。

这不是唯一将未类型化的NULL常量与显式类型的常量区别对待的地方。

另一种情况是

SELECT COALESCE(CAST(NULL AS INT),CAST(NULL AS INT))

vs

SELECT COALESCE(NULL,NULL)

在这种情况下,我会犯错误,而不是“故意”犯错,因为派生表中的列在进入校验和功能之前应该是int

SELECT COALESCE(a,b)
FROM (VALUES(NULL, NULL)) s(a,b)

可以正常工作,而不会出现此故障。