nodes() (método del tipo de datos XML)

El método nodes() es muy útil si desea dividir una instancia de tipo de datos xml en datos relacionales. Permite identificar nodos que se asignarán a una fila nueva.

Cada instancia de tipo de datos xml tiene un nodo de contexto proporcionado de manera implícita. En el caso de la instancia XML almacenada en una columna o variable, éste es el nodo de documento. El nodo de documento es el nodo implícito situado en la parte superior de cada instancia de tipo de datos xml.

El resultado del método nodes() es un conjunto de datos que contiene copias lógicas de las instancias XML originales. En estas copias lógicas, el nodo de contexto de cada instancia de fila se establece en uno de los nodos identificados con la expresión de consulta, de manera que las consultas posteriores pueden desplazarse de forma relativa hasta estos nodos de contexto.

Puede recuperar varios valores del conjunto de filas. Por ejemplo, puede aplicar el método value() al conjunto de filas devuelto por nodes() y recuperar varios valores de la instancia XML original. Tenga en cuenta que el método value(), cuando se aplica a la instancia XML, devuelve sólo un valor.

Ésta es la sintaxis general:

nodes (XQuery) as Table(Column)
  • XQuery
    Es un literal de cadena, una expresión XQuery. Si la expresión de consulta construye nodos, éstos se exponen en el conjunto de filas resultante. Si la expresión de consulta da lugar a una secuencia vacía, el conjunto de filas estará vacío. Si la expresión de consulta da lugar estáticamente a una secuencia que contiene valores atómicos en lugar de nodos, se produce un error estático.
  • Table(Column)
    Es el nombre de tabla y el nombre de columna del conjunto de filas resultante.

Por ejemplo, imagine que tiene la tabla siguiente:

T (ProductModelID int, Instructions xml)

En la tabla se almacena el siguiente documento de instrucciones de fabricación. Sólo se muestra un fragmento. Tenga en cuenta que hay tres ubicaciones de fabricación en el documento.

<root>
  <Location LocationID="10"...>
     <step>...</step>
     <step>...</step>
      ...
  </Location>
  <Location LocationID="20" ...>
       ...
  </Location>
  <Location LocationID="30" ...>
       ...
  </Location>
</root>

La invocación del método nodes() con la expresión de consulta /root/Location devuelve un conjunto con tres filas, de las que cada una contiene una copia lógica del documento XML original, y con el elemento de contexto establecido en uno de los nodos <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>

A continuación, puede consultar este conjunto de filas utilizando métodos del tipo de datos xml. La siguiente consulta extrae el subárbol del elemento de contexto de cada fila generada:

SELECT T2.Loc.query('.')
FROM   T
CROSS APPLY Instructions.nodes('/root/Location') as T2(Loc) 

El resultado es el siguiente:

ProductModelID  Instructions
----------------------------------
1        <Location LocationID="10" ... />
1        <Location LocationID="20" ... />
1        <Location LocationID="30" .../>

Notas

Observe que el conjunto de filas devuelto ha mantenido la información de tipo. Puede aplicar métodos del tipo de datos xml, como query(), value(), exist() y nodes(), al resultado de un método nodes(). Sin embargo, no puede aplicar el método modify() para modificar la instancia XML.

Asimismo, el nodo de contexto del conjunto de filas no se puede materializar. Es decir, no puede utilizarlo en una instrucción SELECT. Sin embargo, puede utilizarlo en IS NULL y COUNT(*).

Los escenarios para utilizar el método nodes() son los mismos que para utilizar OPENXML. Esto proporciona una vista del conjunto de filas del documento XML. Sin embargo, no tiene que utilizar cursores cuando utiliza el método nodes() en una tabla que contenga varias filas de documentos XML.

Observe que el conjunto de filas devuelto por el método nodes() no tiene nombre. Por tanto, se le debe dar un nombre de manera explícita utilizando alias.

La función nodes() no puede aplicarse directamente a los resultados de una función definida por el usuario. Para usar la función nodes() con el resultado de una función escalar definida por el usuario, puede asignar el resultado de la función definida por el usuario a una variable o usar una tabla derivada para asignar un alias de columna al valor devuelto de la función definida por el usuario y luego usar CROSS APPLY para seleccionar del alias.

En el ejemplo siguiente se muestra una forma de usar CROSS APPLY para seleccionar entre los resultados de una función definida por el usuario.

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

Ejemplos

A. Usar el método nodes() con una variable de tipo xml

En el siguiente ejemplo, hay un documento XML que incluye un elemento <Root> de nivel superior y tres elementos secundarios <row>. La consulta utiliza el método nodes() para establecer nodos de contexto independientes, uno para cada elemento <row>. El método nodes() devuelve un conjunto de tres filas. Cada fila tiene una copia lógica del documento original, donde cada nodo de contexto identifica un elemento <row> distinto del documento original.

La consulta devuelve después el nodo de contexto de cada fila:

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

A continuación se muestra el resultado. En este ejemplo, el método de consulta devuelve el elemento de contexto y su contenido:

 <row id="1"><name>Larry</name><oflw>some text</oflw></row>
 <row id="2"><name>moe</name></row>
 <row id="3"/>

Si se aplica el descriptor de acceso primario en los nodos de contexto, devuelve el elemento <Root> para los tres:

SELECT T.c.query('..') AS result
FROM   @x.nodes('/Root/row') T(c)
go

El resultado es el siguiente:

<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>

La siguiente consulta especifica una ruta de acceso absoluta. Una consulta en un nodo de contexto que utiliza una expresión de ruta de acceso absoluta se inicia en el nodo raíz del nodo de contexto. Por tanto, recibirá las tres filas de cada nodo de contexto devuelto por 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" />

Observe que la columna devuelta por el método nodes() del tipo de datos xml no se puede utilizar directamente. Por ejemplo, la consulta siguiente genera un error:

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

En la siguiente consulta, los métodos value() y query() del tipo de datos xml se aplican al conjunto de filas devuelto por el método nodes(). El método value() devuelve el atributo id del elemento de contexto (<row>) y el método query() devuelve el subárbol del elemento <name> del elemento de contexto.

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

El resultado es el siguiente:

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

Tenga en cuenta que el resultado incluye el Id. de fila 3 y que el elemento <row> no tiene un elemento secundario <name>. Si desea filtrar el resultado para que devuelva o no las filas sin el secundario <name>, puede filtrarlo de las siguientes formas:

  • Utilice un predicado en la expresión de ruta de acceso nodes(), por ejemplo /Root/row[name].
  • Utilice el método exist() en el conjunto de filas.
  • Utilice CROSS APPLY.
  • Utilice OUTER APPLY.

En la consulta siguiente se especifica el método exist() para el conjunto de filas devuelto por nodes(). El método exist() devuelve True si el nodo de contexto (<row>) tiene un elemento secundario <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 T1.rows.value('@id','int') as id       
FROM @x.nodes('/Root/row') T1(rows)       
WHERE T1.rows.exist('name') = 1;       
GO

Esto devuelve dos filas, los Id. de fila 1 y 2.

En la siguiente consulta se utiliza OUTER APPLY. OUTER APPLY aplica nodes() a cada fila de T1(rows) y devuelve filas que producen el conjunto de resultados y también NULL. Por tanto, la cláusula WHERE se utiliza para filtrar filas y recuperar únicamente filas en las que la columna T2.names no es 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       

En la siguiente consulta se utiliza CROSS APPLY. CROSS APPLY aplica nodes() a cada fila de la tabla externa, T1(rows), y devuelve sólo las filas que producen un conjunto de resultados cuando nodes() se aplica a T1.rows. En este caso, no tiene que utilizar la cláusula WHERE para probar 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       

Para obtener más información acerca de CROSS APPLY y OUTER APPLY, vea Usar APPLY.

B. Especificar el método nodes() con una columna de tipo xml

En este ejemplo se utilizan las instrucciones de fabricación de bicicletas y se almacenan en la columna Instructions de tipo xml de la tabla ProductModel. Para obtener más información, vea Representación de tipo de datos xml en la base de datos AdventureWorks.

En el siguiente ejemplo, el método nodes() se especifica para la columna Instructions de tipo xml de la tabla ProductModel.

El método nodes() establece los elementos <Location> como nodos de contexto especificando la ruta de acceso /MI:root/MI:Location. El conjunto de filas resultante incluye copias lógicas del documento original, una para cada nodo <Location> del documento, con el nodo de contexto establecido en el elemento <Location>. Por tanto, la función nodes() ofrece un conjunto de nodos de contexto <Location>.

El método query() sobre este conjunto de resultados solicita self::node y, por lo tanto, devuelve el elemento <Location> de cada fila.

En este ejemplo, la consulta establece cada elemento <Location> como un nodo de contexto en el documento de instrucciones de fabricación del modelo de producto específico. Puede utilizar estos nodos de contexto para recuperar valores como los siguientes:

  • Buscar los Id. de ubicación de cada <Location>
  • Recuperar pasos de fabricación (elementos <step> secundarios) en cada <Location>

Esta consulta devuelve el elemento de contexto, en el que se especifica la sintaxis abreviada '.' para self::node(), en el método query().

Tenga en cuenta lo siguiente:

  • El método nodes() se aplica a la columna Instructions y devuelve un conjunto de filas, T (C). Este conjunto de filas contiene copias lógicas del documento de instrucciones de fabricación original con /root/Location como elemento de contexto.

  • CROSS APPLY aplica nodes() a cada fila de la tabla Instructions y devuelve sólo las filas que producen un conjunto de resultados.

    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
    

    Éste es el resultado parcial:

    <MI:Location LocationID="10"  ...>
       <MI:step ... />
          ...
    </MI:Location>
    <MI:Location LocationID="20"  ... >
        <MI:step ... />
          ...
    </MI:Location>
    ...
    

La siguiente consulta es similar a la anterior, con la excepción de que utiliza value() y query() para recuperar un conjunto de valores utilizando los nodos de contexto en el conjunto de filas. Para cada ubicación, la cláusula SELECT recupera el Id. de ubicación (Location ID) y las herramientas utilizadas en esa ubicación.

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

A continuación se muestra el resultado. No se muestran los espacios de nombres para facilitar la lectura.

 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. Aplicar nodes() al conjunto de filas devuelto por otro método nodes()

El código siguiente realiza una consulta en los documentos XML sobre instrucciones de fabricación en la columna Instructions de la tabla ProductModel. La consulta devuelve un conjunto de filas que contiene el Id. del modelo de producto, las ubicaciones y los pasos de fabricación.

Tenga en cuenta lo siguiente:

  • El método nodes() se aplica a la columna Instructions y devuelve el conjunto de filas T1 (Locations). Este conjunto de filas contiene copias lógicas del documento de instrucciones de fabricación original con el elemento /root/Location como contexto del elemento.
  • nodes() se aplica al conjunto de filas T1 (Locations) y devuelve el conjunto de filas T2 (steps). Este conjunto de filas contiene copias lógicas del documento de instrucciones de fabricación original con el elemento /root/Location/step como contexto del elemento.
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       

El resultado es el siguiente:

ProductModelID LocID Step       
----------------------------       
7      10   <step ... />       
7      10   <step ... />       
...       
7      20   <step ... />       
7      20   <step ... />       
7      20   <step ... />       
...       

La consulta declara el prefijo MI dos veces. En su lugar, puede utilizar WITH XMLNAMESPACES para declarar el prefijo una vez y utilizarlo en la consulta:

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  

La siguiente consulta es similar a la anterior, con la excepción de que aplica el método exist() al documento XML en el conjunto de filas T2(steps) para recuperar únicamente los pasos de fabricación que utilizan al menos una herramienta de fabricación. Es decir, el elemento <step> tiene al menos un secundario <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

Vea también

Conceptos

Agregar espacios de nombres mediante WITH XMLNAMESPACES
Tipo de datos xml
Generar instancias XML
Aplicaciones XML de ejemplo

Otros recursos

Métodos de tipo de datos xml

Ayuda e información

Obtener ayuda sobre SQL Server 2005