Поделиться через


Метод nodes() (тип данных xml)

Метод nodes() незаменим в тех случаях, когда экземпляр типа данных xml необходимо разделить на набор реляционных данных. Он позволяет идентифицировать узлы, которые будут ставиться в соответствие новой строке.

У каждого экземпляра типа данных 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>

Вызов метода nodes() с выражением запроса /root/Location возвращает набор из трех строк, каждая из которых содержит логическую копию исходного 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(). Однако для изменения экземпляра XML-метод modify() применять нельзя.

Кроме того, контекстный узел в наборе строк не может быть материализован. Это означает, что он не может использоваться в инструкции SELECT. Однако он может использоваться в выражениях IS NULL и COUNT(*).

Для использования метода nodes() применяются те же сценарии, что и для использования OPENXML. Тем самым обеспечивается представление набора строк XML. Однако при использовании метода nodes() на таблице, содержащей несколько строк XML-документов, нельзя использовать курсоры.

Обратите внимание на то, что набор строк, возвращаемый методом 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. Применение метода nodes() к переменной типа данных xml

В этом примере имеется 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" />

Обратите внимание на то, что столбец, возвращаемый методом nodes() типа данных xml, не может быть использован непосредственно. Например, следующий запрос возвращает ошибку:

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

В следующем запросе методы value() и query() типа данных xml применяются к набору строк, возвращаемому методом nodes(). Метод value() возвращает атрибут id контекстного элемента (<row>), а метод 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   

Обратите внимание на то, что полученный результат включает в себя идентификатор строки 3 и что элемент <row> не имеет дочернего элемента <name>. Если необходимо отфильтровать результат, чтобы возвращались или не возвращались строки без дочернего элемента <name>, можно использовать один из следующих методов:

  • В выражении пути nodes() используйте предикат типа /Root/row[name].
  • Использовать метод exist() на наборе строк.
  • Использовать CROSS APPLY.
  • Использовать OUTER APPLY.

В следующем запросе метод exist() применяется к набору строк, возвращаемому методом nodes(). Метод exist() возвращает значение True, если у контекстного узла (<row>) имеется дочерний элемент <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

Здесь возвращаются две строки с идентификаторами 1 и 2.

В следующем запросе используется инструкция OUTER APPLY, применяющая оператор nodes() к каждой строке в 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 APPLY. Инструкция CROSS APPLY применяет метод nodes() к каждой строке внешней таблицы 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.

Б. Задание метода nodes() на столбце типа данных xml

В этом примере использовано руководство по производству велосипедов, которое хранится в столбце Instructions типа xml в таблице ProductModel. Дополнительные сведения см. в разделе Представление типов xml-данных в базе данных AdventureWorks.

В следующем примере метод nodes() применяется к столбцу Instructions типа xml в таблице ProductModel.

Метод nodes() устанавливает элементы <Location> как контекстные узлы, задавая путь /MI:root/MI:Location. Результирующий набор строк включает в себя логические копии исходного документа, одну для каждого узла <Location> в документе, с контекстным узлом, установленным в элемент <Location>. Таким образом, функция nodes() выдает набор контекстных узлов <Location>.

Метод query(), применяемый к этому набору строк, запрашивает self::node и возвращает элемент <Location> для каждой строки.

В этом примере запрос устанавливает каждый элемент <Location> как контекстный узел в руководстве по изготовлению определенной производственной модели. Эти контекстные узлы можно использовать для извлечения значений следующим образом:

  • Найти идентификаторы LocationID в каждом элементе <Location>
  • Получить шаги производства (дочерние элементы <step>) в каждом элементе <Location>

Этот запрос возвращает контекстный элемент, у которого в методе 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 извлекает идентификатор Location и используемые на этом месте средства.

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

В. Применение метода nodes() к набору строк, возвращенному другим методом nodes()

В следующем коде из столбца Instructions таблицы ProductModel запрашиваются XML-документы, составляющие руководство по изготовлению. Запрос возвращает набор строк, содержащий идентификатор производственной модели, места производства и шаги производства.

Следует отметить следующее:

  • Метод 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  

Следующий запрос аналогичен предыдущему, за исключением того, что в нем для извлечения шагов производства, в которых используется хотя бы один производственный инструмент, к XML-данным в наборе строк T2(steps) применяется метод exist(). Это означает, что у элемента <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