nodes() 方法(xml 数据类型)
如果要将 xml 数据类型实例拆分为关系数据,则 nodes() 方法非常有用。它允许您标识将映射到新行的节点。
每个 xml 数据类型实例都有隐式提供的上下文节点。对于在列或变量中存储的 XML 实例来说,它是文档节点。文档节点是位于每个 xml 数据类型实例顶部的隐式节点。
nodes() 方法的结果是一个包含原始 XML 实例的逻辑副本的行集。在这些逻辑副本中,每个行示例的上下文节点都被设置成由查询表达式标识的节点之一。这样,后续的查询可以浏览与这些上下文节点相关的节点。
您可以从行集中检索多个值。例如,可以将 value() 方法应用于 nodes() 所返回的行集,从原始 XML 实例中检索多个值。注意,当 value() 方法应用于 XML 实例时,它仅返回一个值。
语法
nodes (XQuery) as Table(Column)
参数
XQuery
字符串文字,即一个 XQuery 表达式。如果查询表达式构造节点,这些已构造的节点将在结果行集中显示。如果查询表达式生成一个空序列,则行集将为空。如果查询表达式静态生成一个包含原子值而不是节点的序列,将产生静态错误。Table(Column)
结果行集的表名称和列名称。
注释
例如,假设有下表:
T (ProductModelID int, Instructions xml)
表中存储了以下生产说明文档。仅显示一个片段。注意,文档中有三个生产位置。
<root>
<Location LocationID="10"...>
<step>...</step>
<step>...</step>
...
</Location>
<Location LocationID="20" ...>
...
</Location>
<Location LocationID="30" ...>
...
</Location>
</root>
带有查询表达式 /root/Location 的 nodes() 方法调用将返回一个行集,其中包含三行(每行都有一个原始 XML 文档的逻辑副本),并且上下文项设置为 <Location> 节点之一:
Product
ModelID Instructions
----------------------------------
1 <root>
<Location LocationID="20" ... />
<Location LocationID="30" .../></root>
1 <root><Location LocationID="10" ... />
<Location LocationID="30" .../></root>
1 <root><Location LocationID="10" ... />
<Location LocationID="20" ... />
</root>
然后,可以使用 xml 数据类型方法查询此行集。以下查询为每个生成的行提取上下文项的子树:
SELECT T2.Loc.query('.')
FROM T
CROSS APPLY Instructions.nodes('/root/Location') as T2(Loc)
结果如下:
ProductModelID Instructions
----------------------------------
1 <Location LocationID="10" ... />
1 <Location LocationID="20" ... />
1 <Location LocationID="30" .../>
注意,返回的行集已对类型信息进行了维护。可以将 xml 数据类型方法(例如 query()、value()、exist() 和 nodes())应用于 nodes() 方法的结果。但是,不能将 modify() 方法用于修改 XML 实例。
另外,行集中的上下文节点无法具体化。即,无法在 SELECT 语句中使用此节点。但是,可以在 IS NULL 和 COUNT(*) 中使用它。
使用 nodes() 方法的情况与使用 OPENXML (Transact-SQL) 的情况相同。这就提供了 XML 的行集视图。但是,当您在包含 XML 文档的若干行的表中使用 nodes() 方法时,无需使用游标。
注意,由 nodes() 方法返回的行集是未命名的行集。因此,必须通过使用别名来显式命名。
nodes() 函数不能直接应用于用户定义函数的结果。若要将 nodes() 函数用于标量用户定义函数的结果,可以将该用户定义函数的结果分配给一个变量,也可以使用派生表为该用户定义函数的返回值分配一个列别名,然后使用 CROSS APPLY 从该别名中选择。
以下示例显示了使用 CROSS APPLY 从用户定义函数结果中进行选择的一种方式。
USE AdventureWorks2008R2;
GO
CREATE FUNCTION XTest()
RETURNS xml
AS
BEGIN
RETURN '<document/>';
END;
GO
SELECT A2.B.query('.')
FROM
(SELECT dbo.XTest()) AS A1(X)
CROSS APPLY X.nodes('.') A2(B);
GO
DROP FUNCTION XTest;
GO
示例
针对 xml 类型的变量使用 nodes() 方法
在以下示例中,XML 文档具有一个 <Root> 顶级元素和三个 <row> 子元素。查询使用 nodes() 方法设置单独的上下文节点,每个 <row> 元素一个上下文节点。nodes() 方法返回包含三行的行集。每行都有一个原始 XML 的逻辑副本,其中每个上下文节点都标识原始文档中的一个不同的 <row> 元素。
然后,查询会从每行返回上下文节点:
DECLARE @x xml ;
SET @x='<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
</Root>';
SELECT T.c.query('.') AS result
FROM @x.nodes('/Root/row') T(c);
GO
结果如下:在此示例中,查询方法返回上下文项及其内容:
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3"/>
对上下文节点应用父级取值函数将返回所有三行的 <Root> 元素。
SELECT T.c.query('..') AS result
FROM @x.nodes('/Root/row') T(c);
go
结果如下:
<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
</Root>
<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
</Root>
<Root>
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
</Root>
针对 xml 类型的列指定 nodes() 方法
此示例中使用了自行车生产说明,该说明存储在 ProductModel 表的 Instructions xml 类型列中。有关详细信息,请参阅 AdventureWorks2008R2 数据库中的 xml 数据类型表示形式。
在以下示例中,已对 ProductModel 表中 xml 类型的 Instructions 列指定 nodes() 方法。
nodes() 方法通过指定 /MI:root/MI:Location 路径,将 <Location> 元素设置为上下文节点。结果行集包括原始文档的逻辑副本,每个副本对应文档中的一个 <Location> 节点,上下文节点设置为 <Location> 元素。因此,nodes() 函数提供一组 <Location> 上下文节点。
query() 方法对此行集请求 self::node,从而在每行中返回 <Location> 元素。
在此示例中,查询在特定产品样式的生产说明文档中将每一个 <Location> 元素都设置为上下文节点。您可以使用这些上下文节点来按照以下方式来检索值:
查找每个 <Location> 中的位置 ID
在每个 <Location> 中检索生产步骤(<step> 子元素)
在 query() 方法中,此查询返回上下文项,其中指定了 self::node() 的缩写语法 '.'。
注意以下事项:
将 nodes() 方法应用于 Instructions 列,并且返回行集 T (C)。此行集包含原始生产说明文档的逻辑副本,并且以 /root/Location 作为上下文项。
CROSS APPLY 将 nodes() 应用于 Instructions 表中的每行,并且仅返回生成结果集的行。
SELECT C.query('.') as result FROM Production.ProductModel CROSS APPLY Instructions.nodes(' declare namespace MI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions"; /MI:root/MI:Location') as T(C) WHERE ProductModelID=7
下面是部分结果:
<MI:Location LocationID="10" ...> <MI:step ... /> ... </MI:Location> <MI:Location LocationID="20" ... > <MI:step ... /> ... </MI:Location> ...
将 nodes() 应用于另一 nodes() 方法返回的行集
以下代码查询 XML 文档,以获得 ProductModel 表的 Instructions 列中的生产说明。此查询返回包含产品样式 ID、生产位置和生产步骤的行集。
注意以下事项:
将 nodes() 方法应用于 Instructions 列,并且返回 T1 (Locations) 行集。此行集包含原始生产说明文档的逻辑副本,并且以 /root/Location 元素作为项上下文。
将 nodes() 应用于 T1 (Locations) 行集,并且返回 T2 (steps) 行集。此行集包含原始生产说明文档的逻辑副本,并且 /root/Location/step 元素作为项上下文。
SELECT ProductModelID, Locations.value('./@LocationID','int') as LocID,
steps.query('.') as Step
FROM Production.ProductModel
CROSS APPLY Instructions.nodes('
declare namespace MI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";
/MI:root/MI:Location') as T1(Locations)
CROSS APPLY T1.Locations.nodes('
declare namespace MI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";
./MI:step ') as T2(steps)
WHERE ProductModelID=7
GO
结果如下:
ProductModelID LocID Step
----------------------------
7 10 <step ... />
7 10 <step ... />
...
7 20 <step ... />
7 20 <step ... />
7 20 <step ... />
...
该查询声明 MI 前缀两次。替代方法为,使用 WITH XMLNAMESPACES 声明前缀一次,便可将其用于查询:
WITH XMLNAMESPACES (
'https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions' AS MI)
SELECT ProductModelID, Locations.value('./@LocationID','int') as LocID,
steps.query('.') as Step
FROM Production.ProductModel
CROSS APPLY Instructions.nodes('
/MI:root/MI:Location') as T1(Locations)
CROSS APPLY T1.Locations.nodes('
./MI:step ') as T2(steps)
WHERE ProductModelID=7
GO