在T-SQL中是否有一种简单的方法将utf-8编码的varbinary(max)列转换为varchar(max)。类似于CONVERT(varchar(max), [MyDataColumn])
。最好的解决方案是不需要自定义功能。
目前,我在客户端转换数据,但这有一个缺点,即正确的过滤和排序效率不如服务器端。
答案 0 :(得分:4)
SQL-Server不知道UTF-8(至少可以有效使用的所有版本)。有limited support starting with v2014 SP2(以及有关supported versions的一些详细信息)
通过utf-8
从光盘读取BCP
编码文件时(与将内容写入光盘相同)。
重要信息:
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 function。
答案 1 :(得分:0)
您可以使用以下内容将字符串发布到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);
}
答案 2 :(得分:0)
我试图使用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
以下解决方案应适用于任何编码。
有一种棘手的方法可以准确地执行OP的要求。编辑:我发现了在SO(SQL - UTF-8 to varchar/nvarchar Encoding issue)上讨论过的相同方法
过程如下:
SELECT
CAST(
'<?xml version=''1.0'' encoding=''utf-8''?>' +
REPLACE(
REPLACE(
@BinaryValue,
'&',
'&'
),
'<',
'<'
) AS XML
).value('.', 'varchar(max)')
为什么起作用:varbinary和varchar是相同的位字符串-只有解释不同,所以生成的xml确实是utf8编码的位流,并且xml解释器才能够重建正确的utf8编码的字符。
速度差异取决于数据。当使用XML trick
时有很多要替换的地方时,ALTER trick
可以快得多。当文本很少或文本较短时,创建和更改临时表的开销将导致较大的开销。所以:“取决于...”
DECLARE @LongText NVARCHAR(MAX) = N'...<s>o</s>o&me lo>>>ng u>n<<i< &&code text...';
DECLARE @StartXML DATETIME2(7), @EndXML DATETIME2(7), @StartTable DATETIME2(7), @EndTable DATETIME2(7);
drop table if exists
#longTexts,
#longBinaries,
#XMLConverts;
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 #XMLConverts (LongText VARCHAR(MAX) COLLATE Czech_100_CI_AI_SC_UTF8 NOT NULL);
insert into #longTexts --make the long text longer
(LongText)
select
REPLICATE(@LongText, 100000)
from
Data0001.dbo.Numbers --use while if you don't have number table
WHERE
Number BETWEEN 1 AND 100; --make more of them
insert into #longBinaries (LongBinary) select CAST(LongText AS varbinary(max)) from #longTexts;
SET @StartXML = SYSDATETIME();
------------------------------
--MEASURE XML--
INSERT INTO #XMLConverts
(
LongText
)
SELECT
CAST(
'<?xml version=''1.0'' encoding=''utf-8''?>' +
REPLACE(
REPLACE(
LB.LongBinary,
'&',
'&'
),
'<',
'<'
) AS XML
).value('.', 'varchar(max)')
FROM
#longBinaries AS LB;
SET @EndXML = SYSDATETIME();
SET @StartTable = SYSDATETIME();
--------------------------------------------
--MEASURE ALTER TABLE--
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();
SELECT
DATEDIFF(MILLISECOND, @StartXML, @EndXML) AS XML_MS,
DATEDIFF(MILLISECOND, @StartTable, @EndTable) AS ALTER_MS;