Połącz wszystkie wartości tego samego elementu XML za pomocą XPath / XQuery

14

Mam wartość XML taką jak ta:

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

Chcę złączyć wszystkie Iwartości i zwraca je w postaci pojedynczego łańcucha: ABC....

Teraz wiem, że mogę zniszczyć XML, zsumować wyniki jako XML bez węzłów i zastosować .values('text()[1]', ...)do wyniku:

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

Chciałbym jednak to wszystko zrobić przy użyciu tylko metod XPath / XQuery, coś takiego:

SELECT @MyXml. ? ( ? );

Czy istnieje taki sposób?

Powodem, dla którego szukam rozwiązania w tym kierunku, jest fakt, że mój rzeczywisty XML zawiera również inne elementy, na przykład:

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

I chciałbym móc wyodrębnić zarówno Iwartości jako pojedynczy ciąg znaków, jak i Jwartości jako pojedynczy ciąg znaków bez konieczności używania nieporęcznego skryptu dla każdego z nich.

Andriy M.
źródło

Odpowiedzi:

11

To może Ci pomóc:

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

Odbiera wszystkie text()elementy od pierwszego Ri poniżej.

Jeśli chcesz po prostu wszystko, text()co możesz zrobić

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

Jeśli chcesz wartości dla Ii Joddzielne, zrób to zamiast tego.

select @MyXml.query('/R/I/text()').value('.', 'varchar(50)'),
       @MyXml.query('/R/J/text()').value('.', 'varchar(50)')
Mikael Eriksson
źródło
Ten ostatni został mi zaproponowany na czacie, ale uważam, że ten pierwszy jest również bardzo pomocny. Mogę być w stanie wygenerować dane XML w inny sposób, aby móc zastosować pierwszą metodę.
Andriy M
7

W zależności od faktycznej struktury XML można rozważyć użycie takiej pętli:

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

co daje to:

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

Zobacz to skrzypce

Tom V - spróbuj topanswers.xyz
źródło
1
To jest naprawdę dobre. Mogę łatwo dostosować go tak, aby zawierał ograniczniki, kiedy tylko zajdzie taka potrzeba. I nie jest zbyt gadatliwy, aby go używać, tak jak w przypadku, gdy chcę wyodrębnić ciągi znaków zarówno z ogranicznikami, jak i bez nich w jednolity sposób.
Andriy M
0

Jeśli twoje elementy i wartości są naprawdę krótkie i wyraźne, działa to:

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

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

Jednak w przypadku nietrywialnych plików XML może to być problem.

Michael Green
źródło
Elementy mogą być krótkie, ale ogólnie wartości nie są i nie jestem pewien, czy nie będą zawierać takich samych znaków, jak nazwy elementów. Doceń jednak podejście zewnętrzne.
Andriy M,