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

适用于: SQL Server(所有受支持的版本) Azure SQL 数据库 Azure SQL 托管实例

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

PRINT 语句

xml 数据类型方法不能用于 PRINT 语句,如下面的示例所示。 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 方法。

报告错误

报告错误时,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

单一性检查

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

示例:已知单一实例

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

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> 元素的值。

另请参阅