Подтвердить что ты не робот

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.

Любые советы по этой проблеме?

4b9b3361

Ответ 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