使用 xml 数据类型方法的准则

适用于:SQL ServerAzure SQL 数据库Azure SQL 托管实例Microsoft Fabric 中的 SQL 数据库

本文介绍使用 xml 数据类型方法的指南。

PRINT 语句

不能PRINT语句中使用 xml 数据类型方法,如以下示例所示。 xml 数据类型方法被视为子查询,并且不允许在PRINT语句中查询子查询。 因此,下面的示例将返回一个错误:

DECLARE @x XML
SET @x = '<root>Hello</root>'
PRINT @x.value('/root[1]', 'varchar(20)') -- will not work because this is treated as a subquery (select top 1 col from table)

一种解决方案是先将 value() 方法的结果分配给一个 xml 类型的变量,然后在查询中指定该变量。

DECLARE @x XML
DECLARE @c VARCHAR(max)
SET @x = '<root>Hello</root>'
SET @c = @x.value('/root[1]', 'VARCHAR(11)')
PRINT @c

GROUP BY 子句

xml 数据类型方法在内部视为子查询来处理。 由于GROUP BY需要标量且不允许聚合和子查询,因此不能在子句中指定 GROUP BY 数据类型方法。 另一种解决方案是调用用户定义函数,然后在其内部使用 XML 方法。

报告错误

报告错误时,xml 数据类型方法会引发一个如下格式的错误:

Msg errorNumber, Level levelNumber, State stateNumber:
XQuery [database.table.method]: description_of_error

例如:

Msg 2396, Level 16, State 1:
XQuery [xmldb_test.xmlcol.query()]: Attribute may not appear outside of an element

注释

XQuery 解析器引发的解析错误(例如作为 XML 数据类型方法一部分引用的 XML 中的语法错误)会中止当前事务,无论当前会话的设置如何 XACT_ABORT

单一实例检查

如果编译器无法确定在运行时是否保证单一实例,则需要单一实例的位置步骤、函数参数和运算符将返回错误。 此问题经常出现在非类型化数据上。 例如,查找属性时就需要使用单一的父元素。 通过一个用来选择单个父节点的序号即可满足此要求。 用于提取属性值的 node()-value() 组合的计算可能不需要序号规范。 如下例所示。

示例:已知单一实例

在此示例中,nodes() 方法为每个 元素生成一个单独的行。 对 节点进行计算的 value() 方法提取 <book> 值,并且是单一属性。

SELECT nref.value('@genre', 'VARCHAR(max)') LastName
FROM T CROSS APPLY xCol.nodes('//book') AS R(nref)

XML 架构用于对类型化的 XML 进行类型检查。 如果将某个节点指定为 XML 架构中单一的节点,则编译器将使用该信息,并且不会发生任何错误。 否则,需要使用一个用来选择单个节点的序号。 具体而言,使用 descendant-or-self 轴 (//) 轴(例如在 /book//title 中)会丢失 <title> 元素的单一性基数推理,即使 XML 架构指定其如此。 因此,应将其重写为 (/book//title)[1]

请务必了解类型检查之间的差异 //first-name[1](//first-name)[1] 类型检查。 前者返回一组 <first-name> 节点,其中每个节点都是其同级节点中最左侧的 <first-name> 节点。 后者返回 XML 实例中按文档顺序排列的第一个单一的 <first-name> 节点。

示例:使用 value()

对非类型化的 XML 列的以下查询会导致静态编译错误。 这是因为 value() 需要单一实例节点作为第一个参数,编译器无法确定在运行时是否只发生一个 <last-name> 节点:

SELECT xCol.value('//author/last-name', 'NVARCHAR(50)') LastName
FROM T

可以考虑下面的解决办法:

SELECT xCol.value('//author/last-name[1]', 'NVARCHAR(50)') LastName
FROM T

但是,此解决方案无法解决此错误,因为每个 XML 实例中可能会发生多个 <author> 节点。 采用下面的重写代码可以解决问题:

SELECT xCol.value('(//author/last-name/text())[1]', 'NVARCHAR(50)') LastName
FROM T

此查询返回每个 XML 实例中第一个 <last-name> 元素的值。