SQL Server 2008 и HashBytes
У меня довольно большой nvarchar, который я хочу передать функции HashBytes.
Я получаю сообщение об ошибке:
"Строка или двоичная информация будет усечена. Невозможно вставить значение NULL в column 'colname', tbale 'table'; столбец не допускает нулей. ОБНОВИТЬ выходит из строя. Заявление было прекращается".
Будучи всегда изобретательным, я обнаружил, что это связано с тем, что функция HashBytes имеет максимальный предел в 8000 байт. Дальнейший поиск показал мне "решение", где мой большой varchar будет разделен и хэширован отдельно, а затем в сочетании с этой пользовательской функцией:
function [dbo].[udfLargeHashTable] (@algorithm nvarchar(4), @InputDataString varchar(MAX))
RETURNS varbinary(MAX)
AS
BEGIN
DECLARE
@Index int,
@InputDataLength int,
@ReturnSum varbinary(max),
@InputData varbinary(max)
SET @ReturnSum = 0
SET @Index = 1
SET @InputData = convert(binary,@InputDataString)
SET @InputDataLength = DATALENGTH(@InputData)
WHILE @Index <= @InputDataLength
BEGIN
SET @ReturnSum = @ReturnSum + HASHBYTES(@algorithm, SUBSTRING(@InputData, @Index, 8000))
SET @Index = @Index + 8000
END
RETURN @ReturnSum
END
с которым я звоню:
set @ReportDefinitionHash=convert(int,dbo.[udfLargeHashTable]('SHA1',@ReportDefinitionForLookup))
Где @ReportDefinitionHash - int, а @ReportDefinitionForLookup - это varchar
Передача простого char как "test" создает другой int с моим UDF, чем обычный вызов HashBytes.
Любые советы по этой проблеме?
Ответы
Ответ 1
Просто используйте эту функцию (взято из Хеширование больших строк данных с пользовательской функцией):
create function dbo.fn_hashbytesMAX
( @string nvarchar(max)
, @Algo varchar(10)
)
returns varbinary(20)
as
/************************************************************
*
* Author: Brandon Galderisi
* Last modified: 15-SEP-2009 (by Denis)
* Purpose: uses the system function hashbytes as well
* as sys.fn_varbintohexstr to split an
* nvarchar(max) string and hash in 8000 byte
* chunks hashing each 8000 byte chunk,,
* getting the 40 byte output, streaming each
* 40 byte output into a string then hashing
* that string.
*
*************************************************************/
begin
declare @concat nvarchar(max)
,@NumHash int
,@HASH varbinary(20)
set @NumHash = ceiling((datalength(@string)/2)/(4000.0))
/* HashBytes only supports 8000 bytes so split the string if it is larger */
if @NumHash>1
begin
-- # * 4000 character strings
;with a as (select 1 as n union all select 1) -- 2
,b as (select 1 as n from a ,a a1) -- 4
,c as (select 1 as n from b ,b b1) -- 16
,d as (select 1 as n from c ,c c1) -- 256
,e as (select 1 as n from d ,d d1) -- 65,536
,f as (select 1 as n from e ,e e1) -- 4,294,967,296 = 17+ TRILLION characters
,factored as (select row_number() over (order by n) rn from f)
,factors as (select rn,(rn*4000)+1 factor from factored)
select @concat = cast((
select right(sys.fn_varbintohexstr
(
hashbytes(@Algo, substring(@string, factor - 4000, 4000))
)
, 40) + ''
from Factors
where rn <= @NumHash
for xml path('')
) as nvarchar(max))
set @HASH = dbo.fn_hashbytesMAX(@concat ,@Algo)
end
else
begin
set @HASH = convert(varbinary(20), hashbytes(@Algo, @string))
end
return @HASH
end
И результаты следующие:
select
hashbytes('sha1', N'test') --native function with nvarchar input
,hashbytes('sha1', 'test') --native function with varchar input
,dbo.fn_hashbytesMAX('test', 'sha1') --Galderisi function which casts to nvarchar input
,dbo.fnGetHash('sha1', 'test') --your function
Вывод:
0x87F8ED9157125FFC4DA9E06A7B8011AD80A53FE1
0xA94A8FE5CCB19BA61C4C0873D391E987982FBBD3
0x87F8ED9157125FFC4DA9E06A7B8011AD80A53FE1
0x00000000AE6DBA4E0F767D06A97038B0C24ED720662ED9F1
Ответ 2
Если вы не можете создать функцию и должны использовать что-то, что уже существует в БД:
sys.fn_repl_hash_binary
можно заставить работать с использованием синтаксиса:
sys.fn_repl_hash_binary(cast('some really long string' as varbinary(max)))
Взято из: http://www.sqlnotes.info/2012/01/16/generate-md5-value-from-big-data/
Ответ 3
Я принял принятый ответ и немного изменил его со следующими улучшениями:
- больше не рекурсивная функция
- теперь привязана к схеме
- больше не полагается на недокументированные хранимые процедуры
- две версии: одна для nvarchar, одна для varchar
- возвращает тот же размер данных, что и HASHBYTES, оставляя его до конечного пользователя для преобразования в меньший, основываясь на используемом алгоритме. Это позволяет функциям поддерживать будущие алгоритмы с большими объемами данных.
С этими изменениями функции теперь могут использоваться в постоянных вычисляемых столбцах, поскольку они теперь отмечены детерминированными при создании.
CREATE FUNCTION dbo.fnHashBytesNVARCHARMAX
(
@Algorithm VARCHAR(10),
@Text NVARCHAR(MAX)
)
RETURNS VARBINARY(8000)
WITH SCHEMABINDING
AS
BEGIN
DECLARE @NumHash INT;
DECLARE @HASH VARBINARY(8000);
SET @NumHash = CEILING(DATALENGTH(@Text) / (8000.0));
/* HashBytes only supports 8000 bytes so split the string if it is larger */
WHILE @NumHash > 1
BEGIN
-- # * 4000 character strings
WITH a AS
(SELECT 1 AS n UNION ALL SELECT 1), -- 2
b AS
(SELECT 1 AS n FROM a, a a1), -- 4
c AS
(SELECT 1 AS n FROM b, b b1), -- 16
d AS
(SELECT 1 AS n FROM c, c c1), -- 256
e AS
(SELECT 1 AS n FROM d, d d1), -- 65,536
f AS
(SELECT 1 AS n FROM e, e e1), -- 4,294,967,296 = 17+ TRILLION characters
factored AS
(SELECT ROW_NUMBER() OVER (ORDER BY n) rn FROM f),
factors AS
(SELECT rn, (rn * 4000) + 1 factor FROM factored)
SELECT @Text = CAST
(
(
SELECT CONVERT(VARCHAR(MAX), HASHBYTES(@Algorithm, SUBSTRING(@Text, factor - 4000, 4000)), 1)
FROM factors
WHERE rn <= @NumHash
FOR XML PATH('')
) AS NVARCHAR(MAX)
);
SET @NumHash = CEILING(DATALENGTH(@Text) / (8000.0));
END;
SET @HASH = CONVERT(VARBINARY(8000), HASHBYTES(@Algorithm, @Text));
RETURN @HASH;
END;
CREATE FUNCTION dbo.fnHashBytesVARCHARMAX
(
@Algorithm VARCHAR(10),
@Text VARCHAR(MAX)
)
RETURNS VARBINARY(8000)
WITH SCHEMABINDING
AS
BEGIN
DECLARE @NumHash INT;
DECLARE @HASH VARBINARY(8000);
SET @NumHash = CEILING(DATALENGTH(@Text) / (8000.0));
/* HashBytes only supports 8000 bytes so split the string if it is larger */
WHILE @NumHash > 1
BEGIN
-- # * 4000 character strings
WITH a AS
(SELECT 1 AS n UNION ALL SELECT 1), -- 2
b AS
(SELECT 1 AS n FROM a, a a1), -- 4
c AS
(SELECT 1 AS n FROM b, b b1), -- 16
d AS
(SELECT 1 AS n FROM c, c c1), -- 256
e AS
(SELECT 1 AS n FROM d, d d1), -- 65,536
f AS
(SELECT 1 AS n FROM e, e e1), -- 4,294,967,296 = 17+ TRILLION characters
factored AS
(SELECT ROW_NUMBER() OVER (ORDER BY n) rn FROM f),
factors AS
(SELECT rn, (rn * 8000) + 1 factor FROM factored)
SELECT @Text = CAST
(
(
SELECT CONVERT(VARCHAR(MAX), HASHBYTES(@Algorithm, SUBSTRING(@Text, factor - 8000, 8000)), 1)
FROM factors
WHERE rn <= @NumHash
FOR XML PATH('')
) AS NVARCHAR(MAX)
);
SET @NumHash = CEILING(DATALENGTH(@Text) / (8000.0));
END;
SET @HASH = CONVERT(VARBINARY(8000), HASHBYTES(@Algorithm, @Text));
RETURN @HASH;
END;
Ответ 4
Вы можете написать функцию CLR SQL:
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlBinary BigHashBytes(SqlString algorithm, SqlString data)
{
var algo = HashAlgorithm.Create(algorithm.Value);
var bytes = Encoding.UTF8.GetBytes(data.Value);
return new SqlBinary(algo.ComputeHash(bytes));
}
И тогда он может быть вызван в SQL следующим образом:
--these return the same value
select HASHBYTES('md5', 'test stuff')
select dbo.BigHashBytes('md5', 'test stuff')
BigHashBytes
требуется только в том случае, если длина будет больше 8k.
Ответ 5
проверено и работает
выберите master.sys.fn_repl_hash_binary (someVarbinaryMaxValue)
кроме того, не сложно:)
Ответ 6
Это также можно использовать как тело функции:
DECLARE @A NVARCHAR(MAX) = N'test'
DECLARE @res VARBINARY(MAX) = 0x
DECLARE @position INT = 1
,@len INT = DATALENGTH(@A)
WHILE 1 = 1
BEGIN
SET @res = @res + HASHBYTES('SHA2_256', SUBSTRING(@A, @position, 4000))
SET @position = @position+4000
IF @Position > @len
BREAK
END
SELECT HASHBYTES('SHA2_256',@res)
Идея si для HASH
каждой 4000
части строки NVARCHAR(MAX)
и конкатенации результатов. Тогда к HASH
последнему результату.
Ответ 7
Кажется, самым легким решением является запись рекурсивного алгоритма хэширования, который анализирует входное текстовое значение в сегменты sub varchar(8000)
.
Я произвольно решил нарезать входную строку на 7500 символьных сегментов
Алгоритм хеширования возвращает varbinary(20)
, который можно легко преобразовать в varchar(20)
ALTER FUNCTION [dbo].[BigHash]
(
@TextValue nvarchar(max)
)
RETURNS varbinary(20)
AS
BEGIN
if @TextValue = null
return hashbytes('SHA1', 'null')
Declare @FirstPart as varchar(7500)
Declare @Remainder as varchar(max)
Declare @RemainderHash as varbinary(20)
Declare @BinaryValue as varbinary(20)
Declare @TextLength as integer
Set @TextLength = len(@TextValue)
if @TextLength > 7500
Begin
Set @FirstPart = substring(@TextValue, 1, 7500)
Set @Remainder = substring(@TextValue, 7501, @TextLength - 7500)
Set @RemainderHash = dbo.BigHash(@Remainder)
Set @BinaryValue = hashbytes('SHA1', @FirstPart + convert( varchar(20), @RemainderHash, 2 ))
return @BinaryValue
End
else
Begin
Set @FirstPart = substring(@TextValue, 1, @TextLength)
Set @BinaryValue = hashbytes('SHA1', @FirstPart)
return @BinaryValue
End
return null
END