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

kmbjn2e3  于 2022-10-31  发布在  其他
关注(0)|答案(3)|浏览(553)

有没有一种简单的方法可以在T-SQL中将一个utf-8编码的varbinary(max)列转换为varchar(max)。类似于CONVERT(varchar(max), [MyDataColumn])。最好的解决方案是不需要自定义函数。目前,我在客户端转换数据,但这有一个缺点,正确的过滤和排序不如服务器端高效。

clj7thdc

clj7thdc1#

SQL-Server不知道UTF-8(至少不知道您可以有效使用的所有版本)。透过BCP从光碟读取utf-8编码的档案时,会出现limited support starting with v2014 SP2(以及一些关于supported versions的详细数据)(将内容写入光碟时也是一样)。
重要须知:
VARCHAR(x)不是utf-8。它是 *1字节编码的 * 扩展ASCII,使用代码页(位于归类中)作为字符Map。
NVARCHAR(x)不是utf-16(但非常接近,它是ucs-2。这是一个 *2字节编码 * 的字符串,几乎涵盖了所有已知字符(但存在例外)。
utf-8将使用1个字节用于 * 纯拉丁 * 字符,但使用2个或更多字节用于编码的外来字符集。
一个VARBINARY(x)将把utf-8作为一个无意义的字节链保存。
简单的CASTCONVERT将不起作用:VARCHAR会把每个字节当作一个字符。当然这不是你所期望的结果。NVARCHAR会把每个2字节的块当作一个字符。同样不是你所需要的。
您可以尝试将其写入一个文件,然后使用BCP(v2014 SP2或更高版本)将其读取回来。

ecfdbz9o

ecfdbz9o2#

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,它将破坏多字节字符(取决于排序规则)。
BEWARE 2XML无法处理某些字符,例如0x2。当您的字符串包含此类字符时,此技巧将失败。

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

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

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

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

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

(don't use this,it is necessary,see Plain insert trick)不要使用这个,这是不必要的,请参阅 Plain insert trick
我试图想出一种使用SQL Server 2019的Utf8排序规则的方法,到目前为止,我已经找到了一种可能的方法,它应该比XML技巧更快(见下文)。
1.创建具有varbinary列的临时表。
1.将varbinary值插入到表中
1.使用utf8归类将表alter列更改为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技巧 * 比较慢。
    • 修改表格的技巧 * 是愚蠢的,不要这样做。当你一次修改很多短文本时,它会失败(修改的表格很大)。

测试:

  • 第一个字符串包含一个 *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 ;
1cosmwyk

1cosmwyk3#

您可以使用以下命令将字符串发送到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);
        }

相关问题