Поиск node порядка в XML-документе в SQL Server
Как найти порядок узлов в документе XML?
У меня есть такой документ:
<value code="1">
<value code="11">
<value code="111"/>
</value>
<value code="12">
<value code="121">
<value code="1211"/>
<value code="1212"/>
</value>
</value>
</value>
и я пытаюсь получить эту вещь в таблице, определенной как
CREATE TABLE values(
code int,
parent_code int,
ord int
)
Сохранение порядка значений из XML-документа (они не могут быть упорядочены по их коду). Я хочу сказать
SELECT code
FROM values
WHERE parent_code = 121
ORDER BY ord
и результаты должны детерминистически быть
code
1211
1212
Я пробовал
SELECT
value.value('@code', 'varchar(20)') code,
value.value('../@code', 'varchar(20)') parent,
value.value('position()', 'int')
FROM @xml.nodes('/root//value') n(value)
ORDER BY code desc
Но он не принимает функцию position()
('position()
' может использоваться только в селекторе предиката или XPath).
Я предполагаю, что это возможно каким-то образом, но как?
Ответы
Ответ 1
Вы можете эмулировать функцию position()
путем подсчета количества узлов-сестер, предшествующих каждому node:
SELECT
code = value.value('@code', 'int'),
parent_code = value.value('../@code', 'int'),
ord = value.value('for $i in . return count(../*[. << $i]) + 1', 'int')
FROM @Xml.nodes('//value') AS T(value)
Вот результат:
code parent_code ord
---- ----------- ---
1 NULL 1
11 1 1
111 11 1
12 1 2
121 12 1
1211 121 1
1212 121 2
Как это работает:
- Предложение
for $i in .
определяет переменную с именем $i
, которая содержит текущий node (.
). Это в основном хак для работы с отсутствием XQuery XSLT-подобной функции current()
.
- Выражение
../*
выбирает всех братьев (детей родителя) текущего node.
- Предикат
[. << $i]
фильтрует список братьев и сестер тем, которые предшествуют (<<
) текущий node ($i
).
- Мы
count()
количество предыдущих братьев и сестер, а затем добавим 1, чтобы получить позицию. Таким образом, первый node (который не имеет предыдущих братьев и сестер) присваивает позицию 1.
Ответ 2
Вы можете получить позицию xml, возвращаемую функцией x.nodes()
, например:
row_number() over (order by (select 0))
Например:
DECLARE @x XML
SET @x = '<a><b><c>abc1</c><c>def1</c></b><b><c>abc2</c><c>def2</c></b></a>'
SELECT
b.query('.'),
row_number() over (partition by 0 order by (select 0))
FROM
@x.nodes('/a/b') x(b)
Ответ 3
Ответ erikkallen абсолютно прав.
Однако, если исходный документ/схема может быть изменена, альтернативой является сохранение позиции/индекса в атрибуте. Я использую сочетание обоих подходов, в зависимости от того, кто "составитель" XML, и типа запросов, которые необходимо выполнить над ним. В конце дня я чаще всего использую XML, за исключением, возможно, "немого хранения" в SQL Server, и обычно я счастлив, когда могу сбрасывать его (XML) для нормализованных таблиц.
Счастливое отношение к невменяемым ограничениям продуктов "корпоративного уровня" - чудеса никогда не заканчиваются.
Ответ 4
В соответствии с этот документ, и этот connect entry это невозможно, но запись Connect содержит два обхода.
Я делаю это так:
WITH n(i) AS (SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9),
o(i) AS (SELECT n3.i * 100 + n2.i * 10 + n1.i FROM n n1, n n2, n n3)
SELECT v.value('@code', 'varchar(20)') AS code,
v.value('../@code', 'varchar(20)') AS parent,
o.i AS ord
FROM o
CROSS APPLY @xml.nodes('/root//value[sql:column("o.i")]') x(v)
ORDER BY o.i
Ответ 5
SQL Server row_number()
фактически принимает столбец xml-nodes для упорядочивания. В сочетании с рекурсивным CTE вы можете сделать это:
declare @Xml xml =
'<value code="1">
<value code="11">
<value code="111"/>
</value>
<value code="12">
<value code="121">
<value code="1211"/>
<value code="1212"/>
</value>
</value>
</value>'
;with recur as (
select
ordr = row_number() over(order by x.ml),
parent_code = cast('' as varchar(255)),
code = x.ml.value('@code', 'varchar(255)'),
children = x.ml.query('./value')
from @Xml.nodes('value') x(ml)
union all
select
ordr = row_number() over(order by x.ml),
parent_code = recur.code,
code = x.ml.value('@code', 'varchar(255)'),
children = x.ml.query('./value')
from recur
cross apply recur.children.nodes('value') x(ml)
)
select *
from recur
where parent_code = '121'
order by ordr
В стороне, вы можете сделать это, и он будет делать то, что вы ожидаете:
select x.ml.query('.')
from @Xml.nodes('value/value')x(ml)
order by row_number() over (order by x.ml)
Почему, если это работает, вы можете просто order by x.ml
напрямую без row_number() over
выйти за меня.
Ответ 6
Я вижу ответ @Ben и... получаю новое разрешение
row_number() over (order by (select null))
а
SELECT value.value('@code', 'varchar(20)') code,
value.value('../@code', 'varchar(20)') parent,
row_number() over (order by (select null))
FROM @xml.nodes('/root//value') n(value)