Поддерживает ли метод `nodes()` порядок документа?

Использует ли метод nodes() типа данных типа xml в узле документа?

Например, если есть такие данные, как:

declare @xml xml
set @xml = '<Fruits><Apple /><Banana /><Orange /><Pear /></Fruits>'

который запрашивается как

select T.c.query('.')
from @xml.nodes('/Fruits/*') T(c)

будут возвращены элементы в порядке документа? Порядок строк, возвращаемых select, как известно, undefined, если предложение order by опущено. Это имеет место для select ... from ... .nodes(), или это исключительный?

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

declare @xml xml
set @xml = '<Data><Element OrderNo="1" /><Element OrderNo="2" />'
  + '<Element OrderNo="3" /><Element OrderNo="4" />'
  + '<Element OrderNo="5" /></Data>'

select * from (
    select T.c.value('.', 'int') OrderNo1, 
      row_number() over (order by @@spid) OrderNo2
    from @xml.nodes('/Data/Element/@OrderNo') T(c)) sq
where OrderNo1 != OrderNo2

Ответ 1

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

Таблица-функция Функция XML Reader вводит XML BLOB в качестве параметра и создает набор строк, представляющий узлы XML в порядке XML-документа. Другие входные параметры могут ограничивать узлы XML, возвращаемые подмножеству XML документ.

Но запрос без order by имеет порядок undefined, поэтому никаких гарантий нет.

Один из способов обойти это - использовать id, сгенерированный табличной функцией в выражении row_number() over(), и использовать сгенерированное число в порядке.

select X.q
from
  (
  select T.c.query('.') as q,
         row_number() over(order by T.c) as rn
  from @xml.nodes('/Fruits/*') T(c)
  ) as X
order by X.rn

Невозможно использовать T.c в order by напрямую. Попытка, которая даст вам

Msg 493, уровень 16, состояние 1, строка 19
Столбец 'c', который был возвращен методом nodes(), не может использоваться напрямую. Его можно использовать только с одним из четырех методов типа данных XML: exist(), nodes(), query() и value() или в IS NULL и IS NOT NULL.

В этой ошибке не упоминалось, что она должна работать с row_number, но это происходит, и это может быть ошибкой, которая может быть исправлена, поэтому приведенный выше код не будет работать. Но до SQL Server 2012 он работает отлично.

Способ получения гарантированного заказа, не полагаясь на недокументированное использование row_number, состоял бы в том, чтобы использовать таблицу чисел, где вы извлекаете узлы по положению.

select T.c.query('.') as q
from Numbers as N
  cross apply @xml.nodes('/Fruits/*[sql:column("N.Number")]') as T(c)
where N.Number between 1 and @xml.value('count(/Fruits/*)', 'int')
order by N.Number