如何在SQL Server中散列表的列?

时间:2018-04-28 02:16:15

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

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

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

我整天都在研究这个问题,但似乎找不到任何合理的方法来挖掘每一行的每一行。 Google& StackOverflow搜索返回如何对数据行执行各种操作,但我找不到有关如何在字段上垂直执行相同操作的内容。

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

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

    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带回一个不同的值,表明它是它自己的唯一集合。

2 个答案:

答案 0 :(得分:1)

新解决方案

编辑:基于一些新信息,即可能有超过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 |
+-------------+----------------+----------------+

答案 1 :(得分: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来运行;相反,您可以使用它来生成代码(除非您需要支持列的数量或名称在运行时可能会有所不同的情况,在这种情况下,您显然需要动态选项)。