【问题标题】:How do I hash a column of a table in SQL Server?如何在 SQL Server 中对表的列进行哈希处理?
【发布时间】:2018-04-28 02:16:14
【问题描述】:

我从外部来源收到原始数据文件,需要对它们进行分析。我将文件加载到表中并将字段设置为 varchars,然后运行一个复杂的 SQL 脚本来进行一些自动分析。我一直试图解决的一个问题是:如何判断一列数据是否与同一个表中的 1 个或多个其他列重复?

我的目标是为每一列设置一个散列、校验和或类似的东西,以查看每一行中列的值按照它们进入的顺序。我有动态 SQL,它根据 INFORMATION_SCHEMA.COLUMNS 中列出的字段循环遍历每个字段(不同的表将具有可变数量的列),因此无需担心如何完成该部分。

我整天都在研究这个,但似乎找不到任何明智的方法来散列字段的每一行。 Google 和 StackOverflow 搜索返回如何对数据行执行各种操作,但我找不到太多关于如何在字段上垂直执行相同操作的信息。

所以,我考虑了 2 种可能性并遇到了 2 个障碍:

  1. HASHBYTES - 使用“FOR XML PATH”(或类似的)抓取每一行并在每行之间使用分隔符,然后使用 HASHBYTES 对长字符串进行哈希处理。不幸的是,这对我不起作用,因为我正在运行 SQL Server 2014,并且 HASHBYTES 限制为 8000 个字符的输入。 (我还可以想象,在具有数百万行、200 多列循环的表上,性能会很糟糕)。
  2. CHECKSUM + CHECKSUM_AGG - 获取每个值的 CHECKSUM,将其转换为整数,然后在结果上使用 CHECKSUM_AGG(因为 CHECKSUM_AGG 需要整数)。这看起来很有希望,但不考虑数据的顺序,在不同的行上返回相同的值。此外,发生碰撞的风险更高。

第二个看起来很有希望,但没有像我希望的那样工作......

declare @t1 table
    (col_1 varchar(5)
    , col_2 varchar(5)
    , col_3 varchar(5));

insert into @t1
values ('ABC', 'ABC', 'ABC')
    , ('ABC', 'ABC', 'BCD')
    , ('BCD', 'BCD', NULL)
    , (NULL, NULL, 'ABC');

select * from @t1; 

select cs_1 = CHECKSUM(col_1)
    , cs_2 = CHECKSUM(col_2)
    , cs_3 = CHECKSUM(col_3)
from @t1;

select csa_1 = CHECKSUM_AGG(CHECKSUM([col_1]))
    , csa_2 = CHECKSUM_AGG(CHECKSUM([col_2]))
    , csa_3 = CHECKSUM_AGG(CHECKSUM([col_3]))
from @t1;

在最后一个结果集中,所有 3 列都返回相同的值:2147449198。

期望的结果:我的目标是编写一些代码,其中 csa_1 和 csa_2 带回相同的值,而 csa_3 带回不同的值,表明它是自己独特的集合。

【问题讨论】:

  • CHECKSUM 和 BINARY_CHECKSUM 函数是非常差的哈希函数;你最好使用 hashbytes(MD5)
  • @MitchWheat 这对我来说非常有用,因为我正在使用相同的功能。是什么让他们很穷?为什么要避免它?非常感谢
  • @JohnLBevan OP 还需要知道整个集合是否相同,而不仅仅是哪些行相同。请参阅下面的解决方案以获取单个列。
  • @PittsburghDBA 这应该是我给的:都一样:sqlfiddle.com/#!18/6cf94/1,不一样:sqlfiddle.com/#!18/6cf94/2
  • @JohnLBevan 不错。下次把它放在答案中:-)

标签: sql sql-server tsql hash sql-server-2014


【解决方案1】:

您可以通过这种方式比较每个列组合,而不是使用哈希:

select case when count(case when column1 = column2 then 1 else null end) = count(1) then 1 else 0 end Column1EqualsColumn2
, case when count(case when column1 = column3 then 1 else null end) = count(1) then 1 else 0 end Column1EqualsColumn3
, case when count(case when column1 = column4 then 1 else null end) = count(1) then 1 else 0 end Column1EqualsColumn4
, case when count(case when column1 = column5 then 1 else null end) = count(1) then 1 else 0 end Column1EqualsColumn5
, case when count(case when column2 = column3 then 1 else null end) = count(1) then 1 else 0 end Column2EqualsColumn3
, case when count(case when column2 = column4 then 1 else null end) = count(1) then 1 else 0 end Column2EqualsColumn4
, case when count(case when column2 = column5 then 1 else null end) = count(1) then 1 else 0 end Column2EqualsColumn5
, case when count(case when column3 = column4 then 1 else null end) = count(1) then 1 else 0 end Column3EqualsColumn4
, case when count(case when column3 = column5 then 1 else null end) = count(1) then 1 else 0 end Column3EqualsColumn5
, case when count(case when column4 = column5 then 1 else null end) = count(1) then 1 else 0 end Column4EqualsColumn5
from myData a 

这是设置代码:

create table myData
(
  id integer not null identity(1,1)
  , column1 nvarchar (32)
  , column2 nvarchar (32)
  , column3 nvarchar (32)
  , column4 nvarchar (32)
  , column5 nvarchar (32)
)

insert myData (column1, column2, column3, column4, column5) 
values ('hello', 'hello', 'no', 'match', 'match')
,('world', 'world', 'world', 'world', 'world')
,('repeat', 'repeat', 'repeat', 'repeat', 'repeat')
,('me', 'me', 'me', 'me', 'me')

这是强制性的SQL Fiddle

另外,为了节省您编写此代码的时间,这里有一些代码可以生成上述代码。此版本还将包含处理两列值为空的情况的逻辑:

declare @tableName sysname = 'myData'
, @sql nvarchar(max) 
;with cte as (
    select name, row_number() over (order by column_id) r
    from sys.columns 
    where object_id = object_id(@tableName, 'U') --filter on our table
    and name not in ('id') --only process for the columns we're interested in
)
select @sql = coalesce(@sql + char(10) + ', ', 'select') + ' case when count(case when ' + quotename(a.name) + ' = ' + quotename(b.name) + ' or (' + quotename(a.name) + ' is null and ' + quotename(b.name) + ' is null) then 1 else null end) = count(1) then 1 else 0 end ' + quotename(a.name + '_' + b.name)
from cte a
inner join cte b
on b.r > a.r
order by a.r, b.r

set @sql = @sql  + char(10) + 'from ' + quotename(@tableName)
print @sql

注意:这并不是说您应该将其作为动态 SQL 运行;相反,您可以使用它来生成代码(除非您需要支持列的数量或名称可能在运行时变化的场景,在这种情况下,您显然需要动态选项)。

【讨论】:

    【解决方案2】:

    新解决方案

    编辑:基于一些新信息,即可能有超过 200 列,我的建议是计算每列的哈希值,但在 ETL 工具中执行。

    基本上,通过一个转换来提供您的数据缓冲区,该转换计算先前计算的哈希与当前列值连接的加密哈希。当您到达流的末尾时,您将为每一列连续生成哈希值,这是每组内容和顺序的代理。

    然后,您几乎可以立即将每个与所有其他进行比较,而不是运行 20,000 次表扫描。

    旧解决方案

    试试这个。基本上,您需要这样的查询来分析每一列与其他列。没有真正可行的基于哈希的解决方案。只需按插入顺序(某种行序号)比较每个集合。如果您有计算上可行的方法,请在摄取期间生成此数字,或在检索期间投影它。

    注意:我在这里随意使用 NULL,将其作为空字符串进行比较。

    declare @t1 table
        (
        rownum int identity(1,1)
        , col_1 varchar(5)
        , col_2 varchar(5)
        , col_3 varchar(5));
    
    insert into @t1
    values ('ABC', 'ABC', 'ABC')
        , ('ABC', 'ABC', 'BCD')
        , ('BCD', 'BCD', NULL)
        , (NULL, NULL, 'ABC');
    
    
    with col_1_sets as
    (
    select
        t1.rownum as col_1_rownum
        , CASE WHEN t2.rownum IS NULL THEN 1 ELSE 0 END AS col_2_miss
        , CASE WHEN t3.rownum IS NULL THEN 1 ELSE 0 END AS col_3_miss
    from
        @t1 as t1
        left join @t1 as t2 on
            t1.rownum = t2.rownum
            AND isnull(t1.col_1, '') = isnull(t2.col_2, '')
        left join @t1 as t3 on
            t1.rownum = t3.rownum
            AND isnull(t1.col_1, '') = isnull(t2.col_3, '')
    ),
    col_1_misses as
    (
    select
        SUM(col_2_miss) as col_2_misses
        , SUM(col_3_miss) as col_3_misses
    from
        col_1_sets
    )
    select
        'col_1' as column_name
        , CASE WHEN col_2_misses = 0 THEN 1 ELSE 0 END AS is_col_2_match
        , CASE WHEN col_3_misses = 0 THEN 1 ELSE 0 END AS is_col_3_match
    from
        col_1_misses
    

    结果:

    +-------------+----------------+----------------+
    | column_name | is_col_2_match | is_col_3_match |
    +-------------+----------------+----------------+
    | col_1       |              1 |              0 |
    +-------------+----------------+----------------+
    

    【讨论】:

    • 使用此解决方案,我必须将 col_1 与其他所有列(可能是 col_200 或更高)外部连接。由于无论如何都需要动态 SQL 并且给定可变的列数,如果我只是做了一个包含一堆“SELECT col_compare = 'col_1', CASE WHEN isnull(col_1,'') = isnull(col_2,'') THEN 1 ELSE 0 END, CASE WHEN isnull(col_1,'') = isnull(col_3,'') THEN 1 ELSE 0 END, ....." 在派生表中,为每个列与其他列,然后按 col_compare 分组并获取每列的 MIN 值?那不是更快吗?
    • 因为你有这么多列,所以在你的 ETL 工具 Pentaho 中连续计算哈希值。我对您的原始帖子发表了类似的评论。然后,在您的大量列中,计算的数量将是微不足道的。我正在更新我的答案。您能否编辑您的问题以反映会有大量列(~200)?
    猜你喜欢
    • 2018-06-18
    • 2018-11-08
    • 1970-01-01
    • 1970-01-01
    • 2010-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-22
    相关资源
    最近更新 更多