Объединить все значения одного и того же элемента XML с помощью XPath / XQuery

14

У меня есть значение XML, как это:

<R>
  <I>A</I>
  <I>B</I>
  <I>C</I>
  ...
</R>

Я хочу , чтобы объединить все Iзначения и возвращает их в виде одной строки: ABC....

Теперь я знаю, что могу уничтожить XML, собрать результаты обратно в виде безузлового XML и применить .values('text()[1]', ...)к результату:

SELECT
  (
    SELECT
      n.n.value('text()[1]', 'varchar(50)') AS [text()]
    FROM
      @MyXml.nodes('/R/I') AS n (n)
    FOR XML
      PATH (''),
      TYPE
  ).value('text()[1]', 'varchar(50)')
;

Однако я хотел бы сделать все это, используя только методы XPath / XQuery, что-то вроде этого:

SELECT @MyXml. ? ( ? );

Есть ли такой способ?

Причина, по которой я ищу решение в этом направлении, заключается в том, что мой настоящий XML также содержит другие элементы, например:

<R>
  <I>A</I>
  <I>B</I>
  <I>C</I>
  ...
  <J>X</J>
  <J>Y</J>
  <J>Z</J>
  ...
</R>

И я хотел бы иметь возможность извлекать как Iзначения в виде одной строки, так и Jзначения в виде одной строки без необходимости использовать громоздкий скрипт для каждого.

Андрей М
источник

Ответы:

11

Это может работать для вас:

select @MyXml.value('/R[1]', 'varchar(50)')

Подбирает все text()элементы с первого Rи снизу.

Если вы просто хотите все, text()что можете

select @MyXml.value('.', 'varchar(50)')

Если вы хотите значения для Iи Jотделить сделать это вместо этого.

select @MyXml.query('/R/I/text()').value('.', 'varchar(50)'),
       @MyXml.query('/R/J/text()').value('.', 'varchar(50)')
Микаэль Эрикссон
источник
Последнее было предложено мне в чате, но я нахожу и первое очень полезным. Я мог бы генерировать данные XML по-другому, чтобы я мог применить первый метод к ним.
Андрей М
7

В зависимости от вашей фактической XML-структуры, вы можете рассмотреть возможность использования такого цикла:

DECLARE @xml XML

SELECT @xml = '<R>
  <I>A</I>
  <I>B</I>
  <I>C</I>
  <J>X</J>
  <J>Y</J>
  <J>Z</J>
</R>'

SELECT 
    Tbl.Col.query('for $i in I return $i').value('.', 'nvarchar(max)'),
    Tbl.Col.query('for $i in J return $i').value('.', 'nvarchar(max)')
FROM @xml.nodes('R') Tbl(Col);

который выводит это:

(No column name) | (No column name) 
---------------  | --------------- 
ABC              | XYZ 

Смотрите эту скрипку

Том V - попробуйте topanswers.xyz
источник
1
Это действительно хорошо. Я могу легко адаптировать его для включения разделителей, когда мне это нужно. И это не слишком многословно для использования, как в случае, если я хочу извлекать строки как с разделителями, так и без них единообразно.
Андрей М
0

Если ваши элементы и значения действительно короткие и разные, это работает:

declare @s varchar(99) = '<R><I>A</I><I>B</I><I>C</I></R>';

select
    @s,
    REPLACE(TRANSLATE ( @s, '<>I/R', '     '), ' ', '');

Однако для нетривиального XML это может быть сложно.

Майкл Грин
источник
Элементы могут быть короткими, но значения в целом не являются, и я не могу быть уверен, что они не будут содержать те же символы, что и имена элементов. Цените подход снаружи коробки, все же.
Андрей М