簡體   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