Concaténer toutes les valeurs du même élément XML à l'aide de XPath / XQuery

14

J'ai une valeur XML comme celle-ci:

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

Je veux concaténer toutes les Ivaleurs et les retourner comme une seule chaîne: ABC....

Maintenant, je sais que je peux déchiqueter le XML, agréger les résultats en tant que XML sans nœud et appliquer .values('text()[1]', ...)au résultat:

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

Cependant, je voudrais faire tout cela en utilisant uniquement des méthodes XPath / XQuery, quelque chose comme ceci:

SELECT @MyXml. ? ( ? );

Existe-t-il un tel moyen?

La raison pour laquelle je recherche une solution dans ce sens est que mon XML actuel contient également d'autres éléments, par exemple:

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

Et je voudrais pouvoir extraire à la fois les Ivaleurs sous forme de chaîne unique et les Jvaleurs sous forme de chaîne unique sans avoir à utiliser un script lourd pour chacune.

Andriy M
la source

Réponses:

11

Cela pourrait fonctionner pour vous:

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

Il reprend tous les text()éléments du premier Ret du dessous.

Si vous voulez juste tout ce que text()vous pouvez faire

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

Si vous souhaitez que les valeurs de Iet Jdistinctes le fassent à la place.

select @MyXml.query('/R/I/text()').value('.', 'varchar(50)'),
       @MyXml.query('/R/J/text()').value('.', 'varchar(50)')
Mikael Eriksson
la source
Le dernier m'a été suggéré dans le chat mais je trouve le premier extrêmement utile aussi. Je pourrais peut-être générer les données XML différemment afin de pouvoir y appliquer la première méthode.
Andriy M
7

En fonction de votre structure XML réelle, vous pouvez envisager d'utiliser une boucle comme celle-ci:

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

qui produit ceci:

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

Voir ce violon

Tom V - essayez topanswers.xyz
la source
1
Ceci est vraiment bon. Je peux facilement l'adapter pour inclure des délimiteurs chaque fois que j'en ai besoin. Et ce n'est pas trop verbeux pour être utilisé comme c'est le cas si je veux extraire les chaînes avec et sans délimiteurs de manière uniforme.
Andriy M
0

Si vos éléments et valeurs sont vraiment courts et distincts, cela fonctionne:

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

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

Pour le XML non trivial, il peut cependant être difficile.

Michael Green
la source
Les éléments peuvent être courts, mais les valeurs en général ne le sont pas, et je ne peux pas être sûr qu'ils ne contiendront pas les mêmes caractères que les noms des éléments. Cependant, appréciez l'approche hors des sentiers battus.
Andriy M