[英]Convert utf-8 encoded varbinary(max) data to nvarchar(max) string
是否有一种简单的方法可以在 T-SQL 中将 utf-8 编码的 varbinary(max) 列转换为 varchar(max)。 类似于CONVERT(varchar(max), [MyDataColumn])
。 最好是不需要自定义功能的解决方案。 目前,我在客户端转换数据,但这有缺点,即正确的过滤和排序不如服务器端完成的高效。
SQL-Server 不知道 UTF-8(至少您可以高效使用的所有版本)。 从 v2014 SP2 开始,在通过BCP
从光盘读取utf-8
编码文件时(与将内容写入光盘相同),支持有限。
重要信息:
VARCHAR(x)
不是utf-8
。 它是1 字节编码的扩展 ASCII,使用代码页(位于排序规则中)作为字符映射。
NVARCHAR(x)
不是utf-16
(但非常接近它,它是ucs-2
) 。 这是一个2 字节编码的字符串,涵盖几乎所有已知字符(但存在例外)。
utf-8
将使用 1 个字节来表示纯拉丁字符,但使用 2 个甚至更多字节来编码外来字符集。
VARBINARY(x)
会将utf-8
为无意义的字节链。
简单的CAST
或CONVERT
将不起作用: VARCHAR
会将每个单个字节作为一个字符。 这肯定不是您期望的结果。 NVARCHAR
会将每个 2 字节的块作为一个字符。 再次不是你需要的东西。
您可以尝试将其写入文件并使用BCP
(v2014 SP2 或更高版本)读回。 但我为您看到的更好的机会是CLR 函数。
以下解决方案适用于任何编码。
有一种棘手的方法可以完全按照 OP 的要求进行操作。 编辑:我找到了在 SO 上讨论的相同方法( SQL - UTF-8 to varchar/nvarchar Encoding issue )
这个过程是这样的:
SELECT
CAST(
'<?xml version=''1.0'' encoding=''utf-8''?><![CDATA[' --start CDATA
+ REPLACE(
LB.LongBinary,
']]>', --we need only to escape ]]>, which ends CDATA section
']]]]><![CDATA[>' --we simply split it into two CDATA sections
) + ']]>' AS XML --finish CDATA
).value('.', 'nvarchar(max)')
工作原理:varbinary 和 varchar 是相同的位串——只有解释不同,所以生成的 xml 确实是 utf8 编码的比特流,而 xml 解释器能够重建正确的 utf8 编码字符。
当心value
函数中的'nvarchar(max)'
。 如果您使用varchar
,它会破坏多字节字符(取决于您的排序规则)。
这很简单。 使用 UTF8 排序规则创建另一个数据库作为默认数据库。 创建将VARBINARY
转换为VARCHAR
函数。 返回的VARCHAR
将具有数据库的UTF8
排序规则。
这是另一个简单的技巧。 创建一个包含一列VARCHAR COLLATE ...UTF8
。 将VARBINARY
数据插入到此表中。 它将被正确保存为UTF8
VARCHAR
。 遗憾的是,内存优化表不能使用UTF8
排序规则......
(不要使用这个,这是不必要的,请参阅普通插入技巧)
我试图想出一种使用 SQL Server 2019 的 Utf8 归类的方法,到目前为止我已经找到了一种可能的方法,它应该比 XML 技巧更快(见下文)。
drop table if exists
#bin,
#utf8;
create table #utf8 (UTF8 VARCHAR(MAX) COLLATE Czech_100_CI_AI_SC_UTF8);
create table #bin (BIN VARBINARY(MAX));
insert into #utf8 (UTF8) values ('Žluťoučký kůň říčně pěl ďábelské ódy za svitu měsíce.');
insert into #bin (BIN) select CAST(UTF8 AS varbinary(max)) from #utf8;
select * from #utf8; --here you can see the utf8 string is stored correctly and that
select BIN, CAST(BIN AS VARCHAR(MAX)) from #bin; --utf8 binary is converted into gibberish
alter table #bin alter column BIN varchar(max) collate Czech_100_CI_AI_SC_UTF8;
select * from #bin; --voialá, correctly converted varchar
alter table #bin alter column BIN nvarchar(max);
select * from #bin; --finally, correctly converted nvarchar
考试:
@TextLengthMultiplier
确定转换文本的长度@TextAmount
决定一次转换多少个------------------
--TEST SETUP
--DECLARE @LongText NVARCHAR(MAX) = N'český jazyk, Tiếng Việt, русский язык, 漢語, ]]>';
--DECLARE @LongText NVARCHAR(MAX) = N'JUST ASCII, for LOLZ------------------------------------------------------';
DECLARE
@TextLengthMultiplier INTEGER = 100000,
@TextAmount INTEGER = 10;
---------------------
-- TECHNICALITIES
DECLARE
@StartCDATA DATETIME2(7), @EndCDATA DATETIME2(7),
@StartTable DATETIME2(7), @EndTable DATETIME2(7),
@StartDB DATETIME2(7), @EndDB DATETIME2(7),
@StartInsert DATETIME2(7), @EndInsert DATETIME2(7);
drop table if exists
#longTexts,
#longBinaries,
#CDATAConverts,
#DBConverts,
#INsertConverts;
CREATE TABLE #longTexts (LongText VARCHAR (MAX) COLLATE Czech_100_CI_AI_SC_UTF8 NOT NULL);
CREATE TABLE #longBinaries (LongBinary VARBINARY(MAX) NOT NULL);
CREATE TABLE #CDATAConverts (LongText VARCHAR (MAX) COLLATE Czech_100_CI_AI_SC_UTF8 NOT NULL);
CREATE TABLE #DBConverts (LongText VARCHAR (MAX) COLLATE Czech_100_CI_AI_SC_UTF8 NOT NULL);
CREATE TABLE #InsertConverts (LongText VARCHAR (MAX) COLLATE Czech_100_CI_AI_SC_UTF8 NOT NULL);
insert into #longTexts --make the long text longer
(LongText)
select
REPLICATE(@LongText, @TextLengthMultiplier)
from
TESTES.dbo.Numbers --use while if you don't have number table
WHERE
Number BETWEEN 1 AND @TextAmount; --make more of them
insert into #longBinaries (LongBinary) select CAST(LongText AS varbinary(max)) from #longTexts;
--sanity check...
SELECT TOP(1) * FROM #longTexts;
------------------------------
--MEASURE CDATA--
SET @StartCDATA = SYSDATETIME();
INSERT INTO #CDATAConverts
(
LongText
)
SELECT
CAST(
'<?xml version=''1.0'' encoding=''utf-8''?><![CDATA['
+ REPLACE(
LB.LongBinary,
']]>',
']]]]><![CDATA[>'
) + ']]>' AS XML
).value('.', 'Nvarchar(max)')
FROM
#longBinaries AS LB;
SET @EndCDATA = SYSDATETIME();
--------------------------------------------
--MEASURE ALTER TABLE--
SET @StartTable = SYSDATETIME();
DROP TABLE IF EXISTS #AlterConverts;
CREATE TABLE #AlterConverts (UTF8 VARBINARY(MAX));
INSERT INTO #AlterConverts
(
UTF8
)
SELECT
LB.LongBinary
FROM
#longBinaries AS LB;
ALTER TABLE #AlterConverts ALTER COLUMN UTF8 VARCHAR(MAX) COLLATE Czech_100_CI_AI_SC_UTF8;
--ALTER TABLE #AlterConverts ALTER COLUMN UTF8 NVARCHAR(MAX);
SET @EndTable = SYSDATETIME();
--------------------------------------------
--MEASURE DB--
SET @StartDB = SYSDATETIME();
INSERT INTO #DBConverts
(
LongText
)
SELECT
FUNCTIONS_ONLY.dbo.VarBinaryToUTF8(LB.LongBinary)
FROM
#longBinaries AS LB;
SET @EndDB = SYSDATETIME();
--------------------------------------------
--MEASURE Insert--
SET @StartInsert = SYSDATETIME();
INSERT INTO #INsertConverts
(
LongText
)
SELECT
LB.LongBinary
FROM
#longBinaries AS LB;
SET @EndInsert = SYSDATETIME();
--------------------------------------------
-- RESULTS
SELECT
DATEDIFF(MILLISECOND, @StartCDATA, @EndCDATA) AS CDATA_MS,
DATEDIFF(MILLISECOND, @StartTable, @EndTable) AS ALTER_MS,
DATEDIFF(MILLISECOND, @StartDB, @EndDB) AS DB_MS,
DATEDIFF(MILLISECOND, @StartInsert, @EndInsert) AS Insert_MS;
SELECT TOP(1) '#CDATAConverts ', * FROM #CDATAConverts ;
SELECT TOP(1) '#DBConverts ', * FROM #DBConverts ;
SELECT TOP(1) '#INsertConverts', * FROM #INsertConverts;
SELECT TOP(1) '#AlterConverts ', * FROM #AlterConverts ;
您可以使用以下内容将字符串发布到 varbinary 字段中
Encoding.Unicode.GetBytes(Item.VALUE)
然后使用以下内容以字符串形式检索数据
public string ReadCString(byte[] cString)
{
var nullIndex = Array.IndexOf(cString, (byte)0);
nullIndex = (nullIndex == -1) ? cString.Length : nullIndex;
return System.Text.Encoding.Unicode.GetString(cString);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.