Concatene todos os valores do mesmo elemento XML usando XPath / XQuery

14

Eu tenho um valor XML como este:

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

Quero concatenar todos os Ivalores e retorná-los como uma única sequência:ABC... .

Agora eu sei que posso fragmentar o XML, agregar os resultados novamente como um XML sem assentimento e aplicar .values('text()[1]', ...)ao resultado:

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)')
;

No entanto, eu gostaria de fazer tudo isso usando apenas métodos XPath / XQuery, algo como isto:

SELECT @MyXml. ? ( ? );

Existe tal maneira?

O motivo pelo qual estou procurando uma solução nessa direção é porque meu XML real também contém outros elementos, por exemplo:

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

E eu gostaria de poder extrair os Ivalores como uma única sequência e os Jvalores como uma única sequência sem precisar usar um script pesado para cada um.

Andriy M
fonte

Respostas:

11

Isso pode funcionar para você:

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

Ele pega todos os text()elementos do primeiroR e abaixo.

Se você só quer tudo o text()que pode fazer

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

Se você quiser os valores para Ie Jseparado, faça isso.

select @MyXml.query('/R/I/text()').value('.', 'varchar(50)'),
       @MyXml.query('/R/J/text()').value('.', 'varchar(50)')
Mikael Eriksson
fonte
O último foi sugerido para mim no chat, mas acho o primeiro extremamente útil também. Talvez eu consiga gerar os dados XML de maneira diferente para poder aplicar o primeiro método a eles.
Andriy M
7

Dependendo da sua estrutura XML real, você pode considerar usar um loop como este:

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

que gera isso:

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

Veja este violino

Tom V - tente topanswers.xyz
fonte
1
Isto é muito bom. Posso adaptá-lo facilmente para incluir delimitadores sempre que preciso. E não é muito detalhado para ser usado, como é o caso de eu querer extrair seqüências de caracteres com e sem delimitadores de maneira uniforme.
Andriy M
0

Se seus elementos e valores são realmente curtos e distintos, isso funciona:

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

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

Para XML não trivial, pode ser difícil.

Michael Green
fonte
Os elementos podem ser curtos, mas os valores em geral não, e não posso ter certeza de que não conterão os mesmos caracteres que os nomes dos elementos. Aprecie a abordagem fora da caixa, no entanto.
Andriy M