由于“UTF-8”属性,将 VARCHAR(MAX) 转换为 XML 时出错

McN*_*ets 6 xml sql-server cast sql-server-2014

我需要深入研究具有类似于此架构的日志表:

CREATE TABLE t (
  id int PRIMARY KEY,
  data varchar(max)
);
Run Code Online (Sandbox Code Playgroud)

data以这种格式存储从 Web 服务接收的 XML 文本:

这是缩小版

<?xml version="1.0" encoding="UTF-8"?>
<PARAM>
  <TAB DIM="30" ID="ZC3D2_1" SIZE="5">
    <LIN NUM = "1">
      <FLD NAME = "ZDOC" TYPE = "Char">Ferran López</FLD>
    </LIN>
  </TAB>
</PARAM>
Run Code Online (Sandbox Code Playgroud)

当我尝试将此文本转换为 XML 时,出现下一个错误:

XML 解析:第 xx 行,字符 48,非法 xml 字符

可以通过删除<xml>标记或至少删除encoding属性来解决。

注意:如果没有像 那样的特殊字符ó,即使我不删除<xml>标签,它也能正常工作。

有没有办法在不替换或删除<xml>标签的情况下将其转换为 XML ?

CAST(REPLACE(data, 'encoding="UTF-8"', '') as XML)
Run Code Online (Sandbox Code Playgroud)

db<>在这里摆弄

更新

服务器整理是:Latin1_General_BIN

但即使我尝试将排序规则更改为我常用的服务器排序规则,它也不起作用。

SELECT
  id, 
  CAST((data COLLATE Latin1_General_CI_AS) as XML)
FROM
  t;
Run Code Online (Sandbox Code Playgroud)

Mik*_*son 7

存储在 varchar(max) 列中的 XML 应如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<PARAM>
  <TAB DIM="30" ID="ZC3D2_1" SIZE="5">
    <LIN NUM = "1">
      <FLD NAME = "ZDOC" TYPE = "Char">Ferran López</FLD>
    </LIN>
  </TAB>
</PARAM>
Run Code Online (Sandbox Code Playgroud)

ó应与双字节值来表示ó

如果您的列中没有存储 UTF-8 编码的字符串,那么正确的方法是在将值转换为 XML 数据类型之前从 XML 中删除编码。


Tib*_*szi 6

我认为你有更深层次的问题。UTF-8 允许比 SQL 服务器中的常规非 Unicode 排序规则更多的字符。因此,为了安全起见,您应该使用具有 UTF-8 排序规则的 SQL Server 2019(我理解这是否由于多种原因不可行/不可取)操作系统使用(尝试)nvarchar 而不是 varchar。

如果您害怕从 varchar 到 nvarchar 的存储增加,您可以使用行压缩。但这需要 SQL Server 2016 之前的企业版。


Sol*_*zky 5

这里发生的事情是:

  1. XML类型在内部将数据存储为 UTF-16 Little Endian(至少在大多数情况下)。源编码是什么并不重要,最终结果将是 UTF-16 LE(并且没有<xml>标签,因此没有encoding="...")。
  2. 将字符串转换为XML
    1. 转换的是字符串的字节而不是字符(稍后将解释差异)
    2. NVARCHAR数据假定为 UTF-16 LE。如果有一个<xml>标签并且它包含该encoding属性,则唯一有效的值是"UTF-16"
    3. VARCHAR当没有<xml>标签时,或者如果存在标签但没有encoding属性,则假定数据位于与数据整理相关联的 8 位代码页中。否则,数据将被解释为在encoding属性中指定的代码页中编码(即使它在与数据整理相关联的代码页中编码的)。
  3. 您的数据很可能被编码为 Windows 代码页 1252(这由数据所在列的排序规则决定,而不是实例甚至数据库的排序规则,但由于您提到实例正在使用Latin1_General_BIN,因此是安全的- 足以假设该列使用相同的排序规则)。
  4. ó代码页 Windows-1252 中字符的代码点是:0xF3
  5. <xml>但是,该标记声明 XML 数据被编码为 UTF-8。
  6. 在UTF-8,0xF3 必须跟着三个字节,每个之间是0x80的为0xBF,但在您的数据它的后面是p,其中有一个值0x70。因此,您会收到“非法 xml 字符”错误(因为它encoding="UTF-8"告诉转换函数字节是有效的 UTF-8 字节;转换没有看到该ó字符)。

您的选择是:

  1. 理想地,该柱将被转换为XML 所述encoding的的属性<xml>标签,或整个<xml>标签本身,将在所述的方式去除。AND,该XML数据类型可以节省空间,如果有重复元件和/或属性名称为它创建了一个内部名称的字典(查找列表)并使用 ID 值记录结构。

  2. [data]列设置为使用 UTF-8 排序规则(SQL Server 2019 中的新增功能,因此不适合您)

  3. [data]列设置为NVARCHAR删除标签的encoding属性<xml>,或整个<xml>标签。

  4. 将传入的字符串转换为 UTF-8 字节。所以ó字符是 UTF-8 中的两个字节:0xC3B3,它出现ó在 Windows-1252 中。

    DECLARE @Good VARCHAR(MAX) = '<?xml version="1.0" encoding="UTF-8"?><a>hell'
            + CONVERT(VARCHAR(MAX), 0xC3B3)
            + '</a>';
    SELECT @Good, CONVERT(XML, @Good)
    -- <?xml version="1.0" encoding="UTF-8"?><a>helló</a>
    --
    -- <a>helló</a>
    
    Run Code Online (Sandbox Code Playgroud)

笔记:

  • 简单地删除标签的encoding属性<xml>或整个<xml>标签不是一种选择。当然,它会在这种特殊情况下工作,但它不会在所有情况下都有效,因为 SQL Server 2014 中的列VARCHAR UTF-8 排序规则不可用。因此,Windows 代码页 1252 中不可用的任何 Unicode 字符都将转换为???(取决于 BMP 字符或补充字符):
    DECLARE @Test VARCHAR(MAX) = '<test>ó - ? - </test>';
    SELECT @Test, CONVERT(XML, @Test);
    -- <test>ó - ? - ??</test>
    --
    -- <test>ó - ? - ??</test>
    
    Run Code Online (Sandbox Code Playgroud)
  • 千万不要只是列的排序规则更改为不同区域/文化。虽然这可能会消除错误,但它只能通过静默消除导致错误的数据来实现。例如:
    DECLARE @Data NVARCHAR(MAX) = N'ó';
    SELECT CONVERT(VARCHAR(MAX), @Data COLLATE Latin1_General_BIN) AS [Latin1_General],
        CONVERT(VARCHAR(MAX), @Data COLLATE Latin1_General_BIN) COLLATE
                 Cyrillic_General_CI_AS AS [Cyrillic];
    /*
    Latin1_General    Cyrillic
    ó                 o
    */
    
    Run Code Online (Sandbox Code Playgroud) “Cyrillic”使用与“Latin1_General”不同的代码页,并且该ó字符在 Cyrillic 代码页上不可用。但是,有一个“最佳拟合”映射,这就是为什么我们最终得到一个o而不是一个?.
  • 您和任何使用 SQL Server 2008 或更高版本的人,确实应该使用_100_级别排序规则。此外,任何在 SQL Server 2012 或更高版本上工作的人都应该使用_100__SC(对于补充字符)结尾的级别排序规则。最后,需要SQL Server 2005或更新,使用一个在结束对二进制归类时_BIN2(参见我的文章在这里,为什么)。
  • 此问题与查询是临时查询还是在存储过程(T-SQL 或 SQLCLR)中无关。