【问题标题】:TSQL "Illegal XML Character" When Converting Varbinary to XML将 Varbinary 转换为 XML 时的 TSQL“非法 XML 字符”
【发布时间】:2018-11-02 17:09:29
【问题描述】:

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

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

CAST(Column AS XML)

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

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

消息 9420,第 16 层,状态 1,第 23 行
XML解析:第1行,第7个字符,非法xml字符

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

测试代码:

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

【问题讨论】:

  • varchar 表示 ASCII,或者至少是单字节编码的文本。 ® 不在不受代码页影响的 0-127 范围内。尝试使用 nvarchar 和 CAST( N'&lt;Test&gt;®&lt;/Test&gt;' as varbinary(max))nvarchar 表示 UTF16 即两个字节,这就是为什么从 varchar 到 varbinary 到 nvarchar 的转换失败。
  • 您为什么要进行任何这些转换?无论您想解决什么问题,混合类型都无济于事。如果您遇到编码错误,请确保始终使用 nvarchar 字段、参数 字符串文字。
  • 顺便说一句,这意味着有效的转换实际上是错误的——它们依赖于在转换为 varbinary 和返回文本时使用相同的编码
  • 最后,为什么 SSIS 包将 text 存储到 varbinary 列中?这只是乞求转换问题。我怀疑最初的作者使用了varchar,遇到了转换问题,而不是使用正确的列排序规则或切换到nvarchar,只是使用varbinary 掩盖了问题。这并没有解决任何问题,只是将编码问题转移给了阅读器。只要没有非拉丁字符(这也不会导致 varchar 出现问题),它就可以工作。当添加第一个非拉丁字符时,boom,阅读器失败
  • 问题的内联转换:CAST(CAST( '&lt;Test&gt;®&lt;/Test&gt;' AS VARBINARY(MAX)) AS NVARCHAR(MAX)) 呃,不要那样做。字符编码的基本规则是使用用于写入的编码进行读取。

标签: sql-server xml tsql casting varbinary


【解决方案1】:

有几件事要知道:

  • SQL-Server 在字符编码方面相当有限。有VARCHAR,它是1字节编码的扩展ASCIINVARCHAR,它是UCS-2(几乎和utf-16一样)。
  • VARCHAR 对第一组字符使用 plain latin,对第二组字符使用由正在使用的排序规则提供的代码页映射。
  • VARCHAR 不是 utf-8utf-8VARCHAR 一起使用,只要所有字符都是 1 字节编码的。但是utf-8 知道很多 2 字节编码(最多 4 字节编码)的字符,这会破坏VARCHAR 字符串的内部存储。
  • NVARCHAR 几乎可以原生处理任何 2 字节编码字符(这意味着几乎可以处理任何现有字符)。但不完全是utf-16(有 3 字节的编码字符,这会破坏 SQL-Server 的内部存储)。
  • XML 不是存储为您看到的 XML 字符串,而是存储为基于NVARCHAR 值的分层组织的物理表。
  • 原生存储的 XML 速度非常快,而任何基于文本的存储都需要预先进行非常昂贵的解析操作(一遍又一遍...)。
  • 将 XML 存储为字符串不好,将 XML 存储为 VARCHAR 字符串更糟糕。
  • VARCHAR-string-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 中有许多零。这是由于nvarchar2 字节编码,此示例中不需要每个第二个字节。但如果你需要远东字符,情况就完全不同了。

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

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

你可以尝试什么:

将您的二进制文件转换为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

这可行,但不会很快...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-16
    相关资源
    最近更新 更多