有没有一个简单的方法在 T-SQL 中将 utf-8 编码的 varbinary(max) 列转换为 varchar(max)?类似于 CONVERT(varchar(max), [MyDataColumn])
。最好不需要自定义函数。
目前我在客户端上转换数据,但这样做的缺点是正确的过滤和排序不如在服务器端完成的那么高效。
有没有一个简单的方法在 T-SQL 中将 utf-8 编码的 varbinary(max) 列转换为 varchar(max)?类似于 CONVERT(varchar(max), [MyDataColumn])
。最好不需要自定义函数。
目前我在客户端上转换数据,但这样做的缺点是正确的过滤和排序不如在服务器端完成的那么高效。
以下解决方案适用于任何编码。
有一个巧妙的方法可以确切地做到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)')
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 ;
utf-8
编码文件时,通过BCP
有有限的支持,从v2014 SP2开始(以及支持的版本的一些详细信息)。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函数。
_SC
排序规则。更改数据库的排序规则是一项非常困难的操作。将这些排序规则写入您需要的每个字符串也是一个非常丑陋和耗时的过程...此外,我认为文档并不绝对精确,因为它将 NVARCHAR
称为 unicode / utf-16 等效物。嗯,在99.9%的情况下是这样,但并不是任何UTF-16字符串都可以通过转换为 VARBINARY
再转换回 NVARCHAR
来工作... - Shnugo您可以使用以下内容将字符串发布到 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);
}
nvarchar(...)
,对于CLOBs使用nvarchar(max)
。不需要任何函数或转换,也没有代码页转换错误的机会。您的客户端代码库或驱动程序将能够轻松存储Unicode文本,无需复杂操作。 - Panagiotis Kanavosnvarchar
的数据库吗?还是只是假设Unicode意味着UTF8? - Panagiotis Kanavosnvarchar
字段,并通过触发器更新它以保存UTF16文本。这将允许您应用JSON函数、对其进行索引、指定排序规则等操作。您可以在表上使用透明压缩(自2016SP1以来即可在SQL Server Express中使用),以减少空间占用并通过减少IO来提高性能。 - Panagiotis Kanavos