繁体   English   中英

将 utf-8 编码的 varbinary(max) 数据转换为 nvarchar(max) 字符串

[英]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为无意义的字节链。

简单的CASTCONVERT将不起作用: VARCHAR会将每个单个字节作为一个字符。 这肯定不是您期望的结果。 NVARCHAR会将每个 2 字节的块作为一个字符。 再次不是你需要的东西。

您可以尝试将其写入文件并使用BCP (v2014 SP2 或更高版本)读回。 但我为您看到的更好的机会是CLR 函数

XML 技巧

以下解决方案适用于任何编码。

有一种棘手的方法可以完全按照 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 ,它会破坏多字节字符(取决于您的排序规则)。

数据库技巧(SQL Server 2019 及更新版本)

这很简单。 使用 UTF8 排序规则创建另一个数据库作为默认数据库。 创建将VARBINARY转换为VARCHAR函数。 返回的VARCHAR将具有数据库的UTF8排序规则。

插入技巧(SQL Server 2019 及更新版本)

这是另一个简单的技巧。 创建一个包含一列VARCHAR COLLATE ...UTF8 VARBINARY数据插入到此表中。 它将被正确保存为UTF8 VARCHAR 遗憾的是,内存优化表不能使用UTF8排序规则......

更改表技巧(SQL Server 2019 及更高版本)

(不要使用这个,这是不必要的,请参阅普通插入技巧

我试图想出一种使用 SQL Server 2019 的 Utf8 归类的方法,到目前为止我已经找到了一种可能的方法,它应该比 XML 技巧更快(见下文)。

  1. 使用 varbinary 列创建临时表。
  2. 将 varbinary 值插入表中
  3. 使用 utf8 排序规则将表更改列更改为 varchar
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 

速度差

  • 数据库技巧插入技巧是最快的。
  • XML 技巧较慢。
  • Alter table 技巧很愚蠢,不要这样做。 当您一次更改大量短文本时,它会丢失(更改后的表格很大)。

考试:

  • 第一个字符串包含XML 技巧的一个替换
  • 第二个字符串是纯 ASCII,没有替换 XML 技巧
  • @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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM