Как мне хэшировать столбец таблицы в SQL Server? - PullRequest
0 голосов
/ 28 апреля 2018

Я получаю необработанные файлы данных из внешних источников, и мне необходимо провести анализ по ним. Я загружаю файлы в таблицу и задаю поля как varchars, затем запускаю сложный сценарий SQL, который выполняет некоторый автоматический анализ. Одна проблема, которую я пытался решить: Как определить, дублирован ли столбец данных с одним или несколькими другими столбцами в той же таблице?

Моя цель - иметь для каждого столбца хэш, контрольную сумму или что-то подобное, которое просматривает значения столбцов в каждой строке в порядке их поступления . У меня есть динамический 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 возвращает другое значение, указывая, что это его собственный уникальный набор.

Ответы [ 2 ]

0 голосов
/ 30 апреля 2018

Вы можете сравнить каждый столбец со списком таким образом, вместо использования хэшей:

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

NB. Это не значит, что вы должны запускать его как динамический SQL; скорее вы можете использовать это для генерации своего кода (если только вам не нужно поддерживать сценарий, в котором количество или имя столбцов могут изменяться во время выполнения, и в этом случае вам, очевидно, понадобится динамический параметр).

0 голосов
/ 28 апреля 2018

НОВОЕ РЕШЕНИЕ

РЕДАКТИРОВАТЬ: Исходя из некоторой новой информации, а именно, что может быть более 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 |
+-------------+----------------+----------------+
...