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

Luk*_*zda 10 sql t-sql sql-server

我想知道为什么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);
Run Code Online (Sandbox Code Playgroud)

输出继电器:

+-----+----+------+-------------+
| 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 |
+-----+----+------+------------+
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

前两行的Ouptut:

+-----+----+------+-----+
| id  | a  |  b   | bc  |
+-----+----+------+-----+
|  1  | 1  | 100  | 116 |
|  2  | 3  |   4  |  52 |
+-----+----+------+-----+
Run Code Online (Sandbox Code Playgroud)

db <>小提琴演示


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

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);
Run Code Online (Sandbox Code Playgroud)

db <>小提琴演示2

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

+-----+----+----+-----+----+----+--------------+-------------+
| id  | a  | b  | id  | a  | b  |    bc_t      |    bc_s     |
+-----+----+----+-----+----+----+--------------+-------------+
|  2  |    |    |  2  |    |    | -2147483640  | -2147483640 |
+-----+----+----+-----+----+----+--------------+-------------+
Run Code Online (Sandbox Code Playgroud)

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

+-----+----+------+-----+------+----+-------------+------------+
| id  | a  |  b   | id  |  a   | b  |    bc_t     |    bc_s    |
+-----+----+------+-----+------+----+-------------+------------+
|  1  |    | 100  |  1  | 100  |    |        100  |        100 |
|  2  |    |      |  2  |      |    | 2147483647  | 2147483647 |
+-----+----+------+-----+------+----+-------------+------------+
Run Code Online (Sandbox Code Playgroud)

备注:

  • 我不是在寻找替代品(HASH_BYTES/MD5/CHECKSUM)

  • 我知道BINARY_CHECKSUM可能导致冲突(两个不同的调用产生相同的输出)这里的情况有点不同

对于此定义,我们说指定类型的空值比较为相等的值.如果表达式列表中的至少一个值发生更改,则表达式校验和也会更改.但是,这不能保证.因此,为了检测值是否已更改,我们建议仅在应用程序可以容忍偶尔错过更改时才使用BINARY_CHECKSUM.

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

编辑:

正如@scsimon 指出它适用于物化表但不适用于cte. db <>小提琴实际表格

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)
Run Code Online (Sandbox Code Playgroud)

对于所有情况,所有列都是INT明确的,CAST它的行为应该如此.

db <> fidde元数据

Gor*_*off 5

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

特别是,NULL派生表中仅包含 typeless 的列是不可比较的,因此它被排除在二进制校验和计算之外。这不会发生在真实的表中,因为所有列都有类型。

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

该代码的行为与类型转换的预期一致:

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

是一个 db<>fiddle。

实际上使用值创建表表明仅包含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);
Run Code Online (Sandbox Code Playgroud)

“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);
Run Code Online (Sandbox Code Playgroud)

它抱怨说:

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

具有讽刺意味的是,如果将结果保存到表中并使用binary_checksum(). 问题似乎是与values()和 数据类型的某些交互 - 但在表中并不是很明显information_schema.columns

令人高兴的消息是,代码应该适用于表,即使它不适用于values()生成的派生表 - 正如此SQL Fiddle 所示。

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

  • 所有情况的数据类型 **[demo](https://dbfiddle.uk/?rdbms=sqlserver_2017&amp;fiddle=0e587d128152cb522e103c1bd31c964b)** 因为我认为所有整数 - 所以它看起来对我来说是一个小故障 (3认同)