Concatene todos los valores del mismo elemento XML utilizando XPath / XQuery

14

Tengo un valor XML como este:

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

Quiero concatenar todos los Ivalores y devolverlos como una sola cadena: ABC....

Ahora sé que puedo destruir el XML, agregar los resultados como un XML sin nodo y aplicarlo .values('text()[1]', ...)al 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)')
;

Sin embargo, me gustaría hacer todo eso usando solo métodos XPath / XQuery, algo como esto:

SELECT @MyXml. ? ( ? );

¿Hay tal manera?

La razón por la que estoy buscando una solución en esta dirección es porque mi XML real también contiene otros elementos, por ejemplo:

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

Y me gustaría poder extraer los Ivalores como una sola cadena y los Jvalores como una sola cadena sin tener que usar un script difícil de manejar para cada uno.

Andriy M
fuente

Respuestas:

11

Esto podría funcionar para usted:

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

Recoge todos los text()elementos del primero Ry de abajo.

Si solo quieres todo lo text()que puedes hacer

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

Si desea los valores para Iy Jseparar, haga esto en su lugar.

select @MyXml.query('/R/I/text()').value('.', 'varchar(50)'),
       @MyXml.query('/R/J/text()').value('.', 'varchar(50)')
Mikael Eriksson
fuente
El último me lo sugirieron en el chat, pero el primero también me resultó extremadamente útil. Es posible que pueda generar los datos XML de manera diferente para poder aplicarle el primer método.
Andriy M
7

Dependiendo de su estructura XML real, podría considerar usar un bucle 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 produce esto:

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

Ver este violín

Tom V - prueba topanswers.xyz
fuente
1
Esto es realmente bueno. Puedo adaptarlo fácilmente para incluir delimitadores siempre que lo necesite. Y no es demasiado detallado para ser utilizado, ya que es en caso de que quiera extraer cadenas con y sin delimitadores de manera uniforme.
Andriy M
0

Si sus elementos y valores son realmente cortos y distintos, esto funciona:

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

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

Sin embargo, para XML no trivial puede tener dificultades.

Michael Green
fuente
Los elementos pueden ser cortos, pero los valores en general no lo son, y no puedo estar seguro de que no contengan los mismos caracteres que los nombres de los elementos. Sin embargo, aprecio el enfoque fuera de la caja.
Andriy M