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)
存储在 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 中删除编码。
我认为你有更深层次的问题。UTF-8 允许比 SQL 服务器中的常规非 Unicode 排序规则更多的字符。因此,为了安全起见,您应该使用具有 UTF-8 排序规则的 SQL Server 2019(我理解这是否由于多种原因不可行/不可取)操作系统使用(尝试)nvarchar 而不是 varchar。
如果您害怕从 varchar 到 nvarchar 的存储增加,您可以使用行压缩。但这需要 SQL Server 2016 之前的企业版。
这里发生的事情是:
XML类型在内部将数据存储为 UTF-16 Little Endian(至少在大多数情况下)。源编码是什么并不重要,最终结果将是 UTF-16 LE(并且没有<xml>标签,因此没有encoding="...")。XML:
NVARCHAR数据假定为 UTF-16 LE。如果有一个<xml>标签并且它包含该encoding属性,则唯一有效的值是"UTF-16"。VARCHAR当没有<xml>标签时,或者如果存在标签但没有encoding属性,则假定数据位于与数据整理相关联的 8 位代码页中。否则,数据将被解释为在encoding属性中指定的代码页中编码(即使它是在与数据整理相关联的代码页中编码的)。Latin1_General_BIN,因此是安全的- 足以假设该列使用相同的排序规则)。ó代码页 Windows-1252 中字符的代码点是:0xF3。<xml>但是,该标记声明 XML 数据被编码为 UTF-8。p,其中有一个值0x70。因此,您会收到“非法 xml 字符”错误(因为它encoding="UTF-8"告诉转换函数字节是有效的 UTF-8 字节;转换没有看到该ó字符)。您的选择是:
理想地,该柱将被转换为XML 和所述encoding的的属性<xml>标签,或整个<xml>标签本身,将在所述的方式去除。AND,该XML数据类型可以节省空间,如果有重复元件和/或属性名称为它创建了一个内部名称的字典(查找列表)并使用 ID 值记录结构。
将[data]列设置为使用 UTF-8 排序规则(SQL Server 2019 中的新增功能,因此不适合您)
将[data]列设置为NVARCHAR,并删除标签的encoding属性<xml>,或整个<xml>标签。
将传入的字符串转换为 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而不是一个?._100_级别排序规则。此外,任何在 SQL Server 2012 或更高版本上工作的人都应该使用_100_以_SC(对于补充字符)结尾的级别排序规则。最后,需要SQL Server 2005或更新,使用一个在结束对二进制归类时_BIN2(参见我的文章在这里,为什么)。