代码之家  ›  专栏  ›  技术社区  ›  Lukasz Szozda

二进制校验和-不同的结果取决于行数

  •  9
  • Lukasz Szozda  · 技术社区  · 6 年前

    我想知道为什么 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);
    

    Ouput:

    +-----+----+------+-------------+
    | 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)的选项

    • 我知道 二进制校验和 可能导致冲突(两个不同的调用产生相同的输出),这里的场景有点不同

    对于这个定义,我们说指定类型的空值, 以相等值进行比较。如果 表达式列表更改,表达式校验和也可以更改。 然而,这并不能保证。因此,要检测值是否 已更改,我们建议仅在 应用程序可以容忍偶尔错过的更改。

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

    编辑:

    AS @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 回复  |  直到 6 年前
        1
  •  4
  •   Gordon Linoff    6 年前

    这与行数无关。这是因为两行版本的某一列中的值总是 NULL . 默认类型 无效的 int 数值常量的默认类型是 int ,所以这些应该是可比较的。但从 values() 派生表,它们(显然)不是完全相同的类型。

    尤其是只有无类型的列 无效的 派生表中的s不可比较,因此它被排除在二进制校验和计算之外。这不会出现在实表中,因为所有列都有类型。

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

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

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

    Here 是db<>小提琴。

    实际创建带有值的表时,建议只使用 无效的 值的类型与具有显式数字的列完全相同。这表明原始代码应该可以工作。但一个明确的演员阵容也解决了这个问题。很奇怪。

    这真的,真的很奇怪。考虑以下事项:

    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() 第二排,但不是第一排。

    这是我的结论。当一列中的所有值都是二进制时,则 二进制校验和( 注意到这一点,列属于“不可分割的数据类型”类别。然后,校验和基于剩余的列。

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

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

    它抱怨:

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

    讽刺的是,这是 如果将结果保存到表中并使用 二进制校验和( . 问题似乎是与 值() 和数据类型——但在 information_schema.columns 表。

    不幸的是,代码应该在表上工作,即使它不工作 值() 生成的派生表--作为 this SQL fiddle演示。

    我还学到了一个栏目 无效的 S真的是无字体的。转让 int A中的数据类型 select into 似乎是在定义表时发生的。“无类型”类型转换为 int .

        2
  •  4
  •   Martin Smith    6 年前

    对于文字 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 
    

    如果你试图得到 BINARY_CHECKSUM 具有

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

    它给出了错误

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

    这不是唯一一个没有输入 无效的 常量的处理方式与显式类型的常量不同。

    另一种情况是

    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)
    

    按预期工作,不会出现这种故障。