[英]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
為無意義的字節鏈。
簡單的CAST
或CONVERT
將不起作用: VARCHAR
會將每個單個字節作為一個字符。 這肯定不是您期望的結果。 NVARCHAR
會將每個 2 字節的塊作為一個字符。 再次不是你需要的東西。
您可以嘗試將其寫入文件並使用BCP
(v2014 SP2 或更高版本)讀回。 但我為您看到的更好的機會是CLR 函數。
以下解決方案適用於任何編碼。
有一種棘手的方法可以完全按照 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
,它會破壞多字節字符(取決於您的排序規則)。
這很簡單。 使用 UTF8 排序規則創建另一個數據庫作為默認數據庫。 創建將VARBINARY
轉換為VARCHAR
函數。 返回的VARCHAR
將具有數據庫的UTF8
排序規則。
這是另一個簡單的技巧。 創建一個包含一列VARCHAR COLLATE ...UTF8
。 將VARBINARY
數據插入到此表中。 它將被正確保存為UTF8
VARCHAR
。 遺憾的是,內存優化表不能使用UTF8
排序規則......
(不要使用這個,這是不必要的,請參閱普通插入技巧)
我試圖想出一種使用 SQL Server 2019 的 Utf8 歸類的方法,到目前為止我已經找到了一種可能的方法,它應該比 XML 技巧更快(見下文)。
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 ;
您可以使用以下內容將字符串發布到 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.