将 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) 的隐式转换