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

ssc*_*enb 4 t-sql sql-server

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

and*_*ero 12

XML技巧

\n

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

\n

有一种棘手的方法可以完全按照OP的要求进行操作。编辑:我发现在 SO 上讨论的相同方法(SQL - UTF-8 to varchar/nvarchar Encoding issues)

\n

过程是这样的:

\n
SELECT\n  CAST(\n    \'<?xml version=\'\'1.0\'\' encoding=\'\'utf-8\'\'?><![CDATA[\' --start CDATA\n    + REPLACE(\n      LB.LongBinary,\n      \']]>\', --we need only to escape ]]>, which ends CDATA section\n      \']]]]><![CDATA[>\' --we simply split it into two CDATA sections\n    ) + \']]>\' AS XML --finish CDATA\n  ).value(\'.\', \'nvarchar(max)\')\n
Run Code Online (Sandbox Code Playgroud)\n

工作原理:varbinary 和 varchar 是相同的位字符串 - 只是解释不同,因此生成的 xml 确实是 utf8 编码的比特流,并且 xml 解释器能够重建正确的 utf8 编码字符。

\n

当心函数\'nvarchar(max)\'中的value。如果您使用varchar,它将破坏多字节字符(取决于您的排序规则)。

\n

注意 2 XML 无法处理某些字符,即 0x2。当你的字符串包含这样的字符时,这个技巧就会失败。

\n

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

\n

这很简单。创建另一个数据库,并将 UTF8 排序规则设置为默认排序规则。创建转换VARBINARY为VARCHAR. 返回的结果VARCHAR将具有UTF8数据库的排序规则。

\n

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

\n

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

\n

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

\n

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

\n

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

\n
    \n
  1. 创建带有 varbinary 列的临时表。
  2. \n
  3. 将 varbinary 值插入表中
  4. \n
  5. 更改表更改列为带有 utf8 排序规则的 varchar
  6. \n
\n
drop table if exists\n  #bin,\n  #utf8;\n    \ncreate table #utf8 (UTF8 VARCHAR(MAX) COLLATE Czech_100_CI_AI_SC_UTF8);\ncreate table #bin (BIN VARBINARY(MAX));\n    \ninsert into #utf8 (UTF8) values (\'\xc5\xbdlu\xc5\xa5ou\xc4\x8dk\xc3\xbd k\xc5\xaf\xc5\x88 \xc5\x99\xc3\xad\xc4\x8dn\xc4\x9b p\xc4\x9bl \xc4\x8f\xc3\xa1belsk\xc3\xa9 \xc3\xb3dy za svitu m\xc4\x9bs\xc3\xadce.\');\ninsert into #bin (BIN) select CAST(UTF8 AS varbinary(max)) from #utf8;\n    \nselect * from #utf8; --here you can see the utf8 string is stored correctly and that\nselect BIN, CAST(BIN AS VARCHAR(MAX)) from #bin; --utf8 binary is converted into gibberish\n    \nalter table #bin alter column BIN varchar(max) collate Czech_100_CI_AI_SC_UTF8;\nselect * from #bin; --voial\xc3\xa1, correctly converted varchar\n    \nalter table #bin alter column BIN nvarchar(max);\nselect * from #bin; --finally, correctly converted nvarchar \n
Run Code Online (Sandbox Code Playgroud)\n

速度差

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

考试:

\n
    \n
  • 第一个字符串包含XML 技巧的一个替换
  • \n
  • 第二个字符串是纯 ASCII,没有替换 XML 技巧
  • \n
  • @TextLengthMultiplier确定转换文本的长度
  • \n
  • @TextAmount确定一次将转换多少个
  • \n
\n
------------------\n--TEST SETUP\n--DECLARE @LongText NVARCHAR(MAX) = N\'\xc4\x8desk\xc3\xbd jazyk, Ti\xe1\xba\xbfng Vi\xe1\xbb\x87t, \xd1\x80\xd1\x83\xd1\x81\xd1\x81\xd0\xba\xd0\xb8\xd0\xb9 \xd1\x8f\xd0\xb7\xd1\x8b\xd0\xba, \xe6\xbc\xa2\xe8\xaa\x9e, ]]>\';\n--DECLARE @LongText NVARCHAR(MAX) = N\'JUST ASCII, for LOLZ------------------------------------------------------\';\n\nDECLARE\n  @TextLengthMultiplier INTEGER = 100000,\n  @TextAmount           INTEGER = 10;\n\n---------------------\n-- TECHNICALITIES\nDECLARE\n  @StartCDATA  DATETIME2(7), @EndCDATA  DATETIME2(7),\n  @StartTable  DATETIME2(7), @EndTable  DATETIME2(7), \n  @StartDB     DATETIME2(7), @EndDB     DATETIME2(7),\n  @StartInsert DATETIME2(7), @EndInsert DATETIME2(7);\n\ndrop table if exists\n  #longTexts,\n  #longBinaries,\n  #CDATAConverts,\n  #DBConverts,\n  #INsertConverts;\n\nCREATE TABLE #longTexts      (LongText   VARCHAR  (MAX) COLLATE Czech_100_CI_AI_SC_UTF8 NOT NULL);\nCREATE TABLE #longBinaries   (LongBinary VARBINARY(MAX)                                 NOT NULL);\nCREATE TABLE #CDATAConverts  (LongText   VARCHAR  (MAX) COLLATE Czech_100_CI_AI_SC_UTF8 NOT NULL);\nCREATE TABLE #DBConverts     (LongText   VARCHAR  (MAX) COLLATE Czech_100_CI_AI_SC_UTF8 NOT NULL);\nCREATE TABLE #InsertConverts (LongText   VARCHAR  (MAX) COLLATE Czech_100_CI_AI_SC_UTF8 NOT NULL);\n\ninsert into #longTexts --make the long text longer\n  (LongText)\nselect\n  REPLICATE(@LongText, @TextLengthMultiplier)\nfrom \n  TESTES.dbo.Numbers --use while if you don\'t have number table\nWHERE\n  Number BETWEEN 1 AND @TextAmount; --make more of them\n\ninsert into #longBinaries (LongBinary) select CAST(LongText AS varbinary(max)) from #longTexts;\n\n--sanity check...\nSELECT TOP(1) * FROM #longTexts;\n\n------------------------------\n--MEASURE CDATA--\nSET @StartCDATA = SYSDATETIME();\nINSERT INTO #CDATAConverts \n  (\n    LongText\n  )\nSELECT\n  CAST(\n    \'<?xml version=\'\'1.0\'\' encoding=\'\'utf-8\'\'?><![CDATA[\'\n    + REPLACE(\n      LB.LongBinary,\n      \']]>\',\n      \']]]]><![CDATA[>\'\n    ) + \']]>\' AS XML\n  ).value(\'.\', \'Nvarchar(max)\')\nFROM\n  #longBinaries AS LB;\nSET @EndCDATA = SYSDATETIME();\n\n--------------------------------------------\n--MEASURE ALTER TABLE--\nSET @StartTable = SYSDATETIME();\nDROP TABLE IF EXISTS #AlterConverts;\nCREATE TABLE #AlterConverts (UTF8 VARBINARY(MAX));\n\nINSERT INTO #AlterConverts \n  (\n    UTF8\n  )\nSELECT\n  LB.LongBinary\nFROM\n  #longBinaries AS LB;\n\nALTER TABLE #AlterConverts ALTER COLUMN UTF8 VARCHAR(MAX) COLLATE Czech_100_CI_AI_SC_UTF8;\n--ALTER TABLE #AlterConverts ALTER COLUMN UTF8 NVARCHAR(MAX);\n\nSET @EndTable = SYSDATETIME();\n\n--------------------------------------------\n--MEASURE DB--\nSET @StartDB = SYSDATETIME();\n\nINSERT INTO #DBConverts \n  (\n    LongText\n  )\nSELECT\n  FUNCTIONS_ONLY.dbo.VarBinaryToUTF8(LB.LongBinary)\nFROM\n  #longBinaries AS LB;\n\nSET @EndDB = SYSDATETIME();\n\n--------------------------------------------\n--MEASURE Insert--\nSET @StartInsert = SYSDATETIME();\n\nINSERT INTO #INsertConverts \n  (\n    LongText\n  )\nSELECT\n  LB.LongBinary\nFROM\n  #longBinaries AS LB;\n\nSET @EndInsert = SYSDATETIME();\n\n--------------------------------------------\n-- RESULTS\nSELECT\n  DATEDIFF(MILLISECOND, @StartCDATA, @EndCDATA) AS CDATA_MS,\n  DATEDIFF(MILLISECOND, @StartTable, @EndTable) AS ALTER_MS,\n  DATEDIFF(MILLISECOND, @StartDB, @EndDB) AS DB_MS,\n  DATEDIFF(MILLISECOND, @StartInsert, @EndInsert) AS Insert_MS;\n\nSELECT TOP(1) \'#CDATAConverts \', * FROM #CDATAConverts ;\nSELECT TOP(1) \'#DBConverts    \', * FROM #DBConverts    ;\nSELECT TOP(1) \'#INsertConverts\', * FROM #INsertConverts;\nSELECT TOP(1) \'#AlterConverts \', * FROM #AlterConverts ;\n
Run Code Online (Sandbox Code Playgroud)\n


Shn*_*ugo 6

SQL Server不知道UTF-8(至少您可以有效使用的所有版本)。有开始v2014 SP2有限的支持(以及有关的一些细节支持版本读取时)utf-8从光盘通过编码文件BCP(同样为内容写入到光盘中)。

重要提示:

VARCHAR(x)是不是utf-8。它是1字节编码的扩展ASCII,使用代码页(存在于归类中)作为字符映射。

NVARCHAR(x)是不是utf-16(但非常接近它,它是ucs-2)。这是一个2字节编码的字符串,几乎覆盖了所有已知字符(但存在例外)。

utf-8对于纯拉丁字符,将使用1个字节,但对已编码的外部字符集则使用2个甚至更多个字节。

A VARBINARY(x)将保留utf-8为无意义的字节链。

简单CAST或CONVERT不起作用:VARCHAR将每个单个字节当作一个字符。当然,这不是您期望的结果。NVARCHAR将每个2字节的块视为一个字符。再次不是您需要的东西。

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