将 UTF-8 varbinary(max) 转换为 varchar(max)

Posted

技术标签:

【中文标题】将 UTF-8 varbinary(max) 转换为 varchar(max)【英文标题】:Convert UTF-8 varbinary(max) to varchar(max) 【发布时间】:2022-01-06 09:41:34 【问题描述】:

我有一个 varbinary(max) 列,其中包含已压缩的 UTF-8 编码文本。我想解压缩这些数据,并使用 SQL Server 的 UTF-8 功能在 T-SQL 中将其作为 varchar(max) 使用。

我正在寻找一种在从 varbinary(max) 转换为 varchar(max) 时指定编码的方法。我设法做到这一点的唯一方法是创建一个带有 UTF-8 排序规则列的表变量,并将 varbinary 数据插入其中。

DECLARE @rv TABLE(
    Res varchar(max) COLLATE Latin1_General_100_CI_AS_SC_UTF8 
)

INSERT INTO @rv
SELECT SUBSTRING(Decompressed, 4, DATALENGTH(Decompressed) - 3) WithoutBOM
FROM
    (SELECT DECOMPRESS(RawResource) AS Decompressed FROM Resource) t

我想知道是否有一种更优雅、更有效的方法,不涉及插入到表变量中。

更新:

将其归结为一个不涉及字节顺序标记或压缩的简单示例:

我有字符串“Hello ????” UTF-8 编码,没有存储在变量 @utf8Binary 中的 BOM

DECLARE @utf8Binary varbinary(max) = 0x48656C6C6F20F09F988A

现在我尝试将其分配给各种基于字符的变量并打印结果:

DECLARE @brokenVarChar varchar(max) = CONVERT(varchar(max), @utf8Binary)
print '@brokenVarChar = ' + @brokenVarChar

DECLARE @brokenNVarChar nvarchar(max) = CONVERT(varchar(max), @utf8Binary)
print '@brokenNVarChar = ' +  @brokenNVarChar 

DECLARE @rv TABLE(
    Res varchar(max) COLLATE Latin1_General_100_CI_AS_SC_UTF8 
)

INSERT INTO @rv
select @utf8Binary

DECLARE @working nvarchar(max)
Select TOP 1 @working = Res from @rv

print '@working = ' + @working

这样的结果是:

@brokenVarChar = Hello 😊
@brokenNVarChar = Hello 😊
@working = Hello ????

所以我能够使用这种间接方法正确解码二进制结果,但我想知道是否有更直接(并且可能更有效)的方法。

【问题讨论】:

至少一个示例(压缩)值会真正帮助我们在这里为您提供帮助。 @Larnu,感谢您的建议。我添加了一个示例脚本。 非常好的问题。 According to the documentation, CAST(@utf8Binary AS varchar(max)) COLLATE Latin1_General_100_CI_AS_SC_UTF8 应该工作,但它没有。 老实说,在我的沙盒环境中,我得到了一些非常奇怪的结果......看看这个animated GIF,其中一个函数的行为是一个数据库发生变化,因为我创建了另一个数据库;跨度> 如果有人对 ADS 行为感兴趣,请Github Issue。 【参考方案1】:

我不喜欢这个解决方案,但我不得不这样做(我最初认为它不起作用,因为 ADS 中似乎存在一个错误)。一种方法是在 UTF8 排序规则中创建一个新数据库,然后将值传递给该数据库中的函数。由于数据库是UTF8排序规则,默认排序规则会和本地不同,返回正确的结果:

CREATE DATABASE UTF8 COLLATE Latin1_General_100_CI_AS_SC_UTF8;
GO
USE UTF8;
GO
CREATE OR ALTER FUNCTION dbo.Bin2UTF8 (@utfbinary varbinary(MAX))
RETURNS varchar(MAX) AS
BEGIN
    RETURN CAST(@utfbinary AS varchar(MAX));
END
GO
USE YourDatabase;
GO
SELECT UTF8.dbo.Bin2UTF8(0x48656C6C6F20F09F988A);

然而,这并不是特别“漂亮”。

【讨论】:

@Tornhoof 如果您想出了不同的解决方案,那么您应该将其作为新答案发布。虽然上面看起来它将作为多行标量函数传递,这可能会降低性能 是的,它可能会更慢,所以没有必要发布新的答案,我认为你的答案仍然是解决问题的最佳方法。 我真诚地希望它不是,不幸的是,@Tornhoof。需要一个单独的数据库的要求不太理想。我什至自己提出了这个问题,看看其他人是否有更好的主意,但没有人这样做。这确实表明它是目前“正确”的解决方案,但我不喜欢它。我希望在下一版本的 SQL Server 中有更好的解决方案。老实说,我可能应该检查一下 Azure 反馈中是否有关于“问题”的票。 嗨@Larnu 最近我偶然发现了一个黑客,即使在较低版本中也可以使用,请参阅我的答案。【参考方案2】:

有一个未记录的 hack:

DECLARE @utf8 VARBINARY(MAX)=0x48656C6C6F20F09F988A;

SELECT CAST(CONCAT('<?xml version="1.0" encoding="UTF-8" ?><![CDATA[',@utf8,']]>') AS XML)
       .value('.','nvarchar(max)');

结果

Hello ?

即使在没有新的 UTF8 排序规则的版本中也可以使用...

更新:将其作为函数调用

这可以很容易地包装在一个标量函数中

CREATE FUNCTION dbo.Convert_UTF8_Binary_To_NVarchar(@utfBinary VARBINARY(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
    RETURN
    (
    SELECT CAST(CONCAT('<?xml version="1.0" encoding="UTF-8" ?><![CDATA[',@utfBinary,']]>') AS XML)
           .value('.','nvarchar(max)')
    );
END
GO

或者像这样作为内联表值函数

CREATE FUNCTION dbo.Convert_UTF8_Binary_To_NVarchar(@utfBinary VARBINARY(MAX))
RETURNS TABLE
AS
    RETURN
    SELECT CAST(CONCAT('<?xml version="1.0" encoding="UTF-8" ?><![CDATA[',@utfBinary,']]>') AS XML)
           .value('.','nvarchar(max)') AS ConvertedString
GO

这可以在FROM 之后使用,或者——更合适——和APPLY一起使用

【讨论】:

不错!我希望你在很久以前提供赏金时能找到它,但如果我能再做一个,我会这样做。 :) 如果你能把它变成一个可调用的函数,那真是锦上添花。 @Larnu 刚刚为蛋糕锦上添花 :-) 现在我只需要等待 24 小时 -_- @Larnu,非常感谢,很抱歉在你需要这个的时候我没有来…… 我个人并不“需要”它,@Shnugo,事实上,我只是不“喜欢”我的回答。使用第二个数据库来获得正确的行为当时并不适合我,但我知道没有它会有办法让它工作;只是无法弄清楚如何。不过,这不需要 2019 年的事实甚至更好。 :)【参考方案3】:
DECLARE @utf8Binary varbinary(max) = 0x48656C6C6F20F09F988A;
DECLARE @brokenNVarChar nvarchar(max) = concat(@utf8Binary, '' COLLATE Latin1_General_100_CI_AS_SC_UTF8);
print '@brokenNVarChar = ' +  @brokenNVarChar;

【讨论】:

以上是关于将 UTF-8 varbinary(max) 转换为 varchar(max)的主要内容,如果未能解决你的问题,请参考以下文章

将 varchar/varbinary 的整个表列转换为 xml

无法在 varbinary(max) 中插入空值并出现错误:不允许从数据类型 nvarchar 到 varbinary(max) 的隐式转换

不允许从数据类型 nvarchar 到 varbinary(max) 的隐式转换

VarBinary(max) 到整数列表

将文件作为 varbinary(max) 保存到 SQL 的问题

使用Angularjs中的Web API将pdf文件作为varbinary(max)插入数据库?