Compute MD5 hash of a UTF8 string

前端 未结 3 785
旧时难觅i
旧时难觅i 2020-11-29 11:00

I have an SQL table in which I store large string values that must be unique. In order to ensure the uniqueness, I have a unique index on a column in which I store a string

相关标签:
3条回答
  • 2020-11-29 11:15

    SQL Server does not natively support using UTF-8 strings, and it hasn't for quite a while. As you noticed, NCHAR and NVARCHAR use UCS-2 rather than UTF-8.

    If you are insistent on using the HASHBYTES function, you must be able to pass the UTF-8 byte[] as VARBINARY from your C# code to preserve the encoding. HASHBYTES accepts VARBINARY in place of NVARCHAR. This could be accomplished with a CLR function that accepts NVARCHAR and returns the results of Encoding.UTF8.GetBytes as VARBINARY.

    With that being said, I strongly suggest keeping these types of business rules isolated within your application rather than the database. Especially since the application is already performing this logic.

    0 讨论(0)
  • 2020-11-29 11:17
    SELECT HashBytes('MD5', CAST (N'中文' COLLATE Latin1_General_100_CI_AI_SC_UTF8 AS varchar(4000)))
    

    It only on sql server 2019

    reference: https://www.mssqltips.com/sqlservertip/6168/impact-of-utf8-support-in-sql-server-2019/

    0 讨论(0)
  • 2020-11-29 11:30

    You need to create a UDF to convert the NVARCHAR data to bytes in UTF-8 Representation. Say it is called dbo.NCharToUTF8Binary then you can do:

    hashbytes('md5', dbo.NCharToUTF8Binary(N'abc', 1))
    

    Here is a UDF which will do that:

    create function dbo.NCharToUTF8Binary(@txt NVARCHAR(max), @modified bit)
    returns varbinary(max)
    as
    begin
    -- Note: This is not the fastest possible routine. 
    -- If you want a fast routine, use SQLCLR
        set @modified = isnull(@modified, 0)
        -- First shred into a table.
        declare @chars table (
        ix int identity primary key,
        codepoint int,
        utf8 varbinary(6)
        )
        declare @ix int
        set @ix = 0
        while @ix < datalength(@txt)/2  -- trailing spaces
        begin
            set @ix = @ix + 1
            insert @chars(codepoint)
            select unicode(substring(@txt, @ix, 1))
        end
    
        -- Now look for surrogate pairs.
        -- If we find a pair (lead followed by trail) we will pair them
        -- High surrogate is \uD800 to \uDBFF
        -- Low surrogate  is \uDC00 to \uDFFF
        -- Look for high surrogate followed by low surrogate and update the codepoint   
        update c1 set codepoint = ((c1.codepoint & 0x07ff) * 0x0800) + (c2.codepoint & 0x07ff) + 0x10000
        from @chars c1 inner join @chars c2 on c1.ix = c2.ix -1
        where c1.codepoint >= 0xD800 and c1.codepoint <=0xDBFF
        and c2.codepoint >= 0xDC00 and c2.codepoint <=0xDFFF
        -- Get rid of the trailing half of the pair where found
        delete c2 
        from @chars c1 inner join @chars c2 on c1.ix = c2.ix -1
        where c1.codepoint >= 0x10000
    
        -- Now we utf-8 encode each codepoint.
        -- Lone surrogate halves will still be here
        -- so they will be encoded as if they were not surrogate pairs.
        update c 
        set utf8 = 
        case 
        -- One-byte encodings (modified UTF8 outputs zero as a two-byte encoding)
        when codepoint <= 0x7f and (@modified = 0 OR codepoint <> 0)
        then cast(substring(cast(codepoint as binary(4)), 4, 1) as varbinary(6))
        -- Two-byte encodings
        when codepoint <= 0x07ff
        then substring(cast((0x00C0 + ((codepoint/0x40) & 0x1f)) as binary(4)),4,1)
        + substring(cast((0x0080 + (codepoint & 0x3f)) as binary(4)),4,1)
        -- Three-byte encodings
        when codepoint <= 0x0ffff
        then substring(cast((0x00E0 + ((codepoint/0x1000) & 0x0f)) as binary(4)),4,1)
        + substring(cast((0x0080 + ((codepoint/0x40) & 0x3f)) as binary(4)),4,1)
        + substring(cast((0x0080 + (codepoint & 0x3f)) as binary(4)),4,1)
        -- Four-byte encodings 
        when codepoint <= 0x1FFFFF
        then substring(cast((0x00F0 + ((codepoint/0x00040000) & 0x07)) as binary(4)),4,1)
        + substring(cast((0x0080 + ((codepoint/0x1000) & 0x3f)) as binary(4)),4,1)
        + substring(cast((0x0080 + ((codepoint/0x40) & 0x3f)) as binary(4)),4,1)
        + substring(cast((0x0080 + (codepoint & 0x3f)) as binary(4)),4,1)
    
        end
        from @chars c
    
        -- Finally concatenate them all and return.
        declare @ret varbinary(max)
        set @ret = cast('' as varbinary(max))
        select @ret = @ret + utf8 from @chars c order by ix
        return  @ret
    
    end
    
    0 讨论(0)
提交回复
热议问题