我想知道为什么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 |
+-----+----+------+-----+
当我想比较两个表/查询时会产生奇怪的结果:
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);
对于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
时,其行为应符合预期。
答案 0 :(得分:4)
这与行数无关。这是因为2行版本的一列中的值始终为NULL
。 NULL
的默认类型为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)
可以正常工作,而不会出现此故障。