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/Locationnodes() 方法调用将返回一个行集,其中包含三行(每行都有一个原始 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 的情形相同。这就提供了 XML 的行集视图。但是,当您在包含 XML 文档的若干行的表中使用 nodes() 方法时,无需使用游标。

注意,由 nodes() 方法返回的行集是未命名的行集。因此,必须通过使用别名来显式命名。

nodes() 函数不能直接应用于用户定义函数的结果。若要将 nodes() 函数用于标量用户定义函数的结果,可以将该用户定义函数的结果分配给一个变量,也可以使用派生表为该用户定义函数的返回值分配一个列别名,然后使用 CROSS APPLY 从该别名中选择。

以下示例显示了使用 CROSS APPLY 从用户定义函数结果中进行选择的一种方式。

USE AdventureWorks;
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

示例

A. 对 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>

下面的查询指定了绝对路径。对使用绝对路径表达式的上下文节点的查询,将从上下文节点的根节点开始进行。因此,您将接收 nodes() 针对每个上下文节点返回的所有三行。

SELECT T.c.query('/Root/row') 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" />
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />
<row id="1"><name>Larry</name><oflw>some text</oflw></row>
<row id="2"><name>moe</name></row>
<row id="3" />

注意,不能直接使用由 xml 数据类型的 nodes() 方法返回的列。例如,下面的查询将返回错误:

...
SELECT T.c
FROM   @x.nodes('/Root/row') T(c)

在以下查询中,将 xml 数据类型的 value()query() 方法应用于由 nodes() 方法返回的行集。value() 方法返回上下文项 (<row>) 的 id 属性,而 query() 方法返回上下文项的 <name> 元素子树。

DECLARE @x xml 
SET @x='
<Root>
    <row id="1"><name>Larry</name><oflw>some text</oflw></row>
    <row id="2"><name>Joe</name></row>
    <row id="3" />
</Root>
'
SELECT T.c.value('@id','int') as id,
       T.c.query('name') as NAME
FROM   @x.nodes('/Root/row') T(c)
GO

结果如下:

 id  NAME
-----------------------
 1   <name>Larry</name>
 2   <name>Joe</name>
 3   

注意,结果包括行 ID 3,且 <row> 元素没有 <name> 子级。如果您希望对结果进行筛选,以便返回(或不返回)不带子 <name> 的行,就可以使用下列方法之一对其进行筛选:

  • nodes() 路径表达式(如 /Root/row[name])中使用谓词。
  • 对行集使用 exist() 方法。
  • 使用 CROSS APPLY。
  • 使用 OUTER APPLY。

以下查询对由 nodes() 返回的行集指定 exist() 方法。如果上下文节点 (<row>) 具有 <name> 子级,则 exist() 方法会返回 True。

DECLARE @x xml        
SET @x='<Root>       
    <row id="1"><name>Larry</name><oflw>some text</oflw></row>       
    <row id="2"><name>Joe</name></row>       
    <row id="3" />       
</Root>'       
SELECT T1.rows.value('@id','int') as id       
FROM @x.nodes('/Root/row') T1(rows)       
WHERE T1.rows.exist('name') = 1;       
GO

此查询将返回两行:行 ID 分别为 1 和 2。

以下查询使用 OUTER APPLYOUTER APPLYnodes() 应用于 T1(rows) 中的每行,并且返回生成结果集和 NULL 的行。因此,WHERE 子句用于筛选行并仅检索 T2.names 列不为 NULL 的行。

DECLARE @x xml        
SET @x='       
<Root>       
    <row id="1"><name>Larry</name><oflw>some text</oflw></row>       
    <row id="2"><name>Joe</name></row>       
    <row id="3" />       
</Root>'       
SELECT T1.rows.value('@id','int') as id       
FROM @x.nodes('/Root/row') T1(rows)       
OUTER APPLY T1.rows.nodes('./name') as T2(names)       
WHERE T2.names IS NOT NULL        
GO       

以下查询使用 CROSS APPLYCROSS APPLYnodes() 应用于外部表 T1(rows) 中的每行,并且仅返回在将 nodes() 应用于 T1.rows 时生成结果集的行。在此情况下,不必使用 WHERE 子句测试 IS NOT NULL。

DECLARE @x xml        
SET @x='<Root>       
    <row id="1"><name>Larry</name><oflw>some text</oflw></row>       
    <row id="2"><name>Joe</name></row>       
    <row id="3" />       
</Root>'       
SELECT T1.rows.value('@id','int') as id       
FROM @x.nodes('/Root/row') T1(rows)       
CROSS APPLY T1.rows.nodes('./name') as T2(names)       
GO       

有关 CROSS APPLY 和 OUTER APPLY 的信息,请参阅使用 APPLY

B. 针对 xml 类型的列指定 nodes() 方法

此示例中使用了自行车生产说明,该说明存储在 ProductModel 表的 Instructions xml 类型列中。有关详细信息,请参阅 AdventureWorks 数据库中的 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>
    ...
    

以下查询与先前的查询类似,但它通过行集中的上下文节点使用 value()query() 来检索一组值。对于每个位置,SELECT 子句检索位置 ID 以及此位置所用的工具。

SELECT C.value('@LocationID','int') as LId,
       C.query('declare namespace MI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";
                 MI:step/MI:tool') 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

以下为结果:为提高可读性,未显示命名空间。

 LId  result
 10  <MI:tool xmlns:MI="...">T-85A framing tool</MI:tool>
     <MI:tool xmlns:MI="...">Trim Jig TJ-26</MI:tool>
     <MI:tool xmlns:MI="...">router with a carbide tip 15</MI:tool>
      <MI:tool xmlns:MI="...">Forming Tool FT-15</MI:tool>
 20
 30  <MI:tool xmlns:MI="...">standard debur tool</MI:tool>
 45  <MI:tool xmlns:MI="...">paint harness</MI:tool>
 50
 60

C. 将 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  

以下查询与先前的查询类似,但它将 exist() 方法应用于 T2(steps) 行集的 XML,以仅检索至少使用一种生产工具的生产步骤。即:<step> 元素至少包含一个 <tool> 子元素。

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 Steps
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
AND    steps.exist('./MI:tool') = 1
GO

请参阅

概念

使用 WITH XMLNAMESPACES 添加命名空间
xml 数据类型
生成 XML 实例
示例 XML 应用程序

其他资源

XML 数据类型方法

帮助和信息

获取 SQL Server 2005 帮助