如果您只想得到答案而没有完整的解释,请向下滚动到“结论”。但是,你真的应该花点时间阅读解释? ;DR>
这里发生了一些事情:
<xml> 元素的encoding= 属性用于表示如何解释 XML 文档的底层字节。如果字符串文字中的文档是正确的,则不需要有encoding 属性。如果存在不正确的字符,则encoding 属性可以保留,因为它将通知 XML 转换这些字符的原始内容。
UTF-8 是一种 Unicode 编码,但变量和文字是 VARCHAR 数据,而不是 NVARCHAR(这还需要在字符串文字前加上大写字母-N)。通过使用VARCHAR 并且不使用N-前缀,如果在执行此查询时,XML 文档中有任何字符无法放入由您所在的任何数据库的默认排序规则表示的代码页中,您将拥有已经丢失了这些字符(即使您可以在屏幕上看到它们,它们在 VARCHAR 变量中也不正确,或者如果您对该文字做了简单的 SELECT)。
-
Windows(和 .NET、SQL Server 等)使用 UTF-16 Little Endian。 Í 字符 Latin Capital Letter I with Acute 存在于代码页 1252 和 UTF-16LE 中作为值 205(例如 SELECT ASCII('Í'), CHAR(205); ),这就是当您删除 encoding="utf-8" 和为什么您没有通过将其放在 VARCHAR 文字和变量中来“丢失”该字符。但是,如该链接页面上所示,UTF-8 编码中的字节序列是 195、141(是的,两个字节)。这意味着,如果该字符确实是 UTF-8 编码的,那么在放入 UTF-16LE 环境时,它看起来不会是该字符。
XML 转换查看该字符的字节值 205(单字节,因为它当前是 VARCHAR 数据)并尝试提供与 UTF-8 中的 那个 序列等效的 UTF-16LE。除了 205 本身在 UTF-8 中不存在。因此,您需要添加下一个字符,即大写字母“A”,其值为 65。虽然 UTF-8 中有两个字节序列,但它们都不是 205、65。这就是为什么你得到illegal xml character 错误。
-
由于屏幕上的文本必须是 UTF-16LE,如果源真的是 UTF-8,那么底层的 UTF-8 字节序列就必须转换成 UTF-16LE。 Í 的底层字节序列是 195、141。因此,我们可以通过执行以下操作从 Code Page 1252 的常规 ASCII 字符(因为这又是当前的 VARCHAR 数据)创建该序列:
DECLARE @poit VARCHAR(100);
SET @poit = '<?xml version="1.0" encoding="UTF-8"?><test>V'
+ CHAR(195) + CHAR(141) + 'A</test>';
SELECT CONVERT(XML, @poit);
返回:
<test>VÍA</test>
数据仍然是VARCHAR 并且 encoding="utf-8" 仍然在<xml> 元素中!
-
如果将数据保留为 VARCHAR,则仅对 encoding= 值进行以下更改即可:
DECLARE @poit VARCHAR(100);
SET @poit = '<?xml version="1.0" encoding="Windows-1252"?><test>VÍA</test>';
SELECT CONVERT(XML, @poit);
这假定源编码确实是“Windows-1252”,这是 Microsoft 的 Latin1_General 版本,它是 Latin1_General 排序规则的基础。
但是,如果它与任何 VARCHAR 数据假定的当前数据库默认排序规则的代码页相同,则甚至不需要指定“编码”。
1234563李>
结论
将 XML 作为字符串处理时使用 NVARCHAR(MAX) 数据类型(而不是 VARCHAR)。
-
对于没有任何更改字符的字符串(即,所有内容在屏幕上看起来都很完美),然后只需删除 encoding="utf-8" 即可。无需将其替换为 UTF-16,因为该值的本质是在 NVARCHAR 变量或文字中(即以大写字母-N 为前缀的字符串)。
关于使用VARCHAR(MAX) 代替XML 甚至NVARCHAR(MAX) 以节省空间,请记住XML 数据类型在内部进行了优化,因此元素和属性名称只存储一次,在字典中,因此几乎没有完全写出的 XML 字符串版本的开销。因此,虽然 XML 类型确实将字符串存储为 UTF-16LE,if XML 文档有很多重复的元素和/或属性名称,然后使用 @987654362 @type 实际上可能比使用VARCHAR(MAX) 占用更少的空间:
DECLARE @ElementBased XML;
SET @ElementBased = (
SELECT * FROM master.sys.all_columns FOR XML PATH('Row')
);
DECLARE @AttributeBased XML;
SET @AttributeBased = (
SELECT * FROM master.sys.all_columns FOR XML RAW('Row')
);
SELECT @ElementBased AS [ElementBasedXML],
@AttributeBased AS [AttributeBasedXML],
DATALENGTH(@ElementBased) AS [ElementBasedXmlBytes],
DATALENGTH(CONVERT(VARCHAR(MAX), @ElementBased)) AS [ElementBasedVarCharBytes],
((DATALENGTH(@ElementBased) * 1.0) / DATALENGTH(CONVERT(VARCHAR(MAX), @ElementBased))
) * 100 AS [XmlElementSizeRelativeToVarcharElementSize],
DATALENGTH(@AttributeBased) AS [AttributeBasedXmlBytes],
DATALENGTH(CONVERT(VARCHAR(MAX), @AttributeBased)) AS [AttributeBasedVarCharBytes],
((DATALENGTH(@AttributeBased) * 1.0) /
DATALENGTH(CONVERT(VARCHAR(MAX), @AttributeBased))) * 100
AS [XmlAttributeSizeRelativeToVarCharAttributeSize];
返回(至少在我的系统上):
ElementBasedXmlBytes 1717896
ElementBasedVarCharBytes 5889081
XmlElementSizeRelativeToVarcharElementSize 29.170867237180130482100
AttributeBasedXmlBytes 1544661
AttributeBasedVarCharBytes 3461864
XmlAttributeSizeRelativeToVarCharAttributeSize 44.619343798600984902900
如您所见,对于基于元素的 XML,XML 数据类型的大小是 VARCHAR(MAX) 版本的 29%,而对于基于属性的 XML,XML 数据类型的大小是VARCHAR(MAX) 版本。