将Varbinary转换为XML时,TSQL“非法XML字符”

时间:2018-11-02 17:09:29

标签: sql-server xml tsql casting varbinary

我正在尝试在SQL Server 2016中创建一个存储过程,该存储过程会将先前转换为Varbinary的XML转换回XML,但是转换时出现“非法XML字符”错误。我找到了一种似乎可行的解决方法,但实际上无法弄清楚为什么为什么起作用,这让我感到不舒服。

存储过程获取在SSIS中转换为二进制并插入表的varbinary(MAX)列中的数据,并执行简单的

CAST(Column AS XML)

它工作了很长时间,当最初的XML开始包含®(注册商标)符号时,我才开始看到一个问题。

现在,当我尝试将二进制文件转换为XML时,出现此错误

  

9420州第1级第23行的消息9
  XML解析:第1行,字符7,非法xml字符

但是,如果我先将二进制文件转换为varchar(MAX),然后再将其转换为XML,它似乎可以正常工作。我不了解执行中间的CAST与直接转换为XML不同时发生了什么。我主要担心的是,我不想将其添加到这种情况下,并且最终会带来意想不到的后果。

测试代码:

DECLARE @foo VARBINARY(MAX)
DECLARE @bar VARCHAR(MAX)
DECLARE @Nbar NVARCHAR(MAX) 

--SELECT Varbinary
SET @foo = CAST( '<Test>®</Test>' AS VARBINARY(MAX)) 
SELECT @foo AsBinary


--select as binary as varchar
SET @bar = CAST(@foo AS VARCHAR(MAX))

SELECT @bar BinaryAsVarchar                             -- Correct string output

--select binary as nvarchar
SET @nbar = CAST(@foo AS NVARCHAR(MAX))
SELECT @nbar BinaryAsNvarchar                           -- Chinese characters 

--select binary as XML
SELECT TRY_CAST(@foo AS XML) BinaryAsXML                -- ILLEGAL XML character
-- SELECT CONVERT(xml, @obfoo) BinaryAsXML                    --ILLEGAL XML Character

--select BinaryAsVarcharAsXML
SELECT TRY_CAST(@bar AS XML) BinaryAsVarcharAsXML       -- Correct Output

--select BinaryAsNVarcharAsXML
SELECT TRY_CAST(@nbar AS XML) BinaryAsNvarcharAsXML     -- Chinese Characters

1 个答案:

答案 0 :(得分:0)

有几件事要知道:

  • SQL-Server的字符编码相当有限。有VARCHAR,它是 1字节编码的扩展ASCII ,还有NVARCHAR,它是UCS-2(与utf-16几乎一样)。
  • VARCHAR plain latin 用于第一组字符,并将排序规则提供的代码页映射用于第二组。
  • VARCHAR 不是utf-8 utf-8VARCHAR一起使用,只要所有字符都以1字节为单位即可。但是utf-8知道很多2字节编码(最多4字节编码)的字符,这会破坏VARCHAR字符串的内部存储。
  • NVARCHAR将在本地几乎可以使用任何2字节编码字符(这意味着几乎可以使用任何现有字符)。但这不是完全utf-16(存在3字节编码字符,这会破坏SQL Server的内部存储)。
  • XML不会存储为您看到的XML字符串,而是存储为基于NVARCHAR值的分层组织的物理表。
  • 本机存储的XML确实非常快,而任何基于文本的存储都将需要事先非常昂贵的解析操作(一遍又一遍...)。
  • 将XML存储为字符串是不好的,将XML存储为VARCHAR字符串更糟。
  • VARCHAR字符串XML存储为VARBINARY是对您不应该做的事情的累加。

尝试一下:

DECLARE @text1Byte VARCHAR(100)='<test>blah</test>';
DECLARE @text2Byte NVARCHAR(100)=N'<test>blah</test>';

SELECT CAST(@text1Byte AS VARBINARY(MAX)) AS text1Byte_Binary
      ,CAST(@text2Byte AS VARBINARY(MAX)) AS text2Byte_Binary
      ,CAST(@text1Byte AS XML) AS text1Byte_XML
      ,CAST(@text2Byte AS XML) AS text2Byte_XML
      ,CAST(CAST(@text1Byte AS VARBINARY(MAX)) AS XML) AS text1Byte_XML_via_Binary
      ,CAST(CAST(@text2Byte AS VARBINARY(MAX)) AS XML) AS text2Byte_XML_via_Binary

您将看到的唯一区别是0x3C0074006500730074003E0062006C00610068003C002F0074006500730074003E00中的许多零。这是由于nvarchar 2字节编码,此示例中不需要每个第二字节。但是,如果您需要远东字符,则情况将完全不同。

它起作用的原因:SQL-Server非常聪明。如引擎所知,从变量到XML的转换非常简单,基础变量是varcharnvarchar。但是最后两个演员不同。引擎必须检查二进制文件是否有效nvarchar,如果失败,将再次尝试使用varchar

现在尝试将您的注册商标添加到给定的示例中。首先将其添加到第二个变量DECLARE @text2Byte NVARCHAR(100)=N'<test>blah®</test>';中,然后尝试运行它。然后将其添加到第一个变量,然后重试。

您可以尝试的方法:

将二进制文件先转换为varchar(max),然后转换为nvarchar(max),最后转换为xml

,CAST(CAST(CAST(CAST(@text1Byte AS VARBINARY(MAX)) AS VARCHAR(MAX)) AS NVARCHAR(MAX)) AS XML) AS text1Byte_XML_via_Binary

这可以工作,但是不会很快...

相关问题