2009-07-15 6 views
15

Wie kann ich die Reihenfolge der Knoten in einem XML-Dokument finden?Suchen der Knotenreihenfolge im XML-Dokument in SQL Server

Was ich habe, ist ein Dokument wie folgt aus:

<value code="1"> 
    <value code="11"> 
     <value code="111"/> 
    </value> 
    <value code="12"> 
     <value code="121"> 
      <value code="1211"/> 
      <value code="1212"/> 
     </value> 
    </value> 
</value> 

und ich versuche, dieses Ding in eine Tabelle wie

CREATE TABLE values(
    code int, 
    parent_code int, 
    ord int 
) 

Erhaltung der Reihenfolge der Werte aus dem XML definiert zu erhalten Dokument (sie können nicht nach ihrem Code sortiert werden). Ich möchte

SELECT code 
FROM values 
WHERE parent_code = 121 
ORDER BY ord 

sagen können, und die Ergebnisse sollten, deterministisch, sein

code 
1211 
1212 

ich versucht habe

SELECT 
    value.value('@code', 'varchar(20)') code, 
    value.value('../@code', 'varchar(20)') parent, 
    value.value('position()', 'int') 
FROM @xml.nodes('/root//value') n(value) 
ORDER BY code desc 

Aber es nicht akzeptieren, die position() Funktion (‘ position() kann nur innerhalb eines Prädikat- oder XPath-Selektors verwendet werden.

Ich denke, es ist irgendwie möglich, aber wie?

+0

Sie endliche Tiefe von Knoten haben Sie? Wenn nicht, wird es ein Schmerz sein. Und um zu bestätigen: Sie können sich nicht auf Codes verlassen? – gbn

+0

... und welche Ausgabe möchten Sie von der XML? – gbn

+0

Ich habe die Frage aktualisiert, um weitere Informationen bereitzustellen. Und nein, es gibt eine unendliche Tiefe. – erikkallen

Antwort

31

Sie können durch Zählen der Anzahl der Geschwisterknoten vor jedem Knoten die position() Funktion emulieren:

SELECT 
    code = value.value('@code', 'int'), 
    parent_code = value.value('../@code', 'int'), 
    ord = value.value('for $i in . return count(../*[. << $i]) + 1', 'int') 
FROM @Xml.nodes('//value') AS T(value) 

Hier ist das Ergebnis Satz:

code parent_code ord 
---- ----------- --- 
1  NULL   1 
11  1   1 
111 11   1 
12  1   2 
121 12   1 
1211 121   1 
1212 121   2 

Wie es funktioniert:

  • Die for $i in .-Klausel definiert eine Variable mit dem Namen $i, die den aktuellen Knoten enthält (.). Dies ist im Grunde ein Hack, um XQuerys Fehlen einer XSLT-ähnlichen current() Funktion zu umgehen.
  • Der Ausdruck ../* wählt alle Geschwister (untergeordnete Elemente) des aktuellen Knotens aus.
  • Das Prädikat [. << $i] filtert die Liste der Geschwister zu denen, die dem aktuellen Knoten vorausgehen (<<) ($i).
  • Wir count() die Anzahl der vorhergehenden Geschwister und dann 1 hinzufügen, um die Position zu erhalten. Auf diese Weise der erste Knoten (die keine vorhergehenden Geschwister hat), um eine Position von 1.
  • zugewiesen
+2

Ich habe diesen Code für eine ziemlich große XML-Datei verwendet und weil die 'für $ i in. return count (../* [. << $ i]) + 1' Teil durchläuft alle "Geschwister" Knoten vor jedem Knoten dauerte dies für immer (wir lassen es auf der Arbeit laufen, während nach Hause ging, war es am nächsten Tag abgestürzt). Seien Sie also gewarnt, dass dieser Code eine O (n^2) Effizienz hat. – funkwurm

2

Nach this Dokument und das connect entry ist es nicht möglich, aber der Connect-Eintrag enthält zwei Problemumgehungen.

Ich mache es wie folgt aus:

WITH n(i) AS (SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9), 
    o(i) AS (SELECT n3.i * 100 + n2.i * 10 + n1.i FROM n n1, n n2, n n3) 
SELECT v.value('@code', 'varchar(20)') AS code, 
     v.value('../@code', 'varchar(20)') AS parent, 
     o.i AS ord 
    FROM o 
CROSS APPLY @xml.nodes('/root//value[sql:column("o.i")]') x(v) 
ORDER BY o.i 
+0

Jedes Mal, wenn ich versuche zu sehen, ob es einen guten Weg gibt, dies zu tun, habe ich immer das Gefühl zu weinen. Es ist der einzige Weg, den ich gefunden habe (tatsächlich benutze ich eine Zahlentabelle, aber denselben hässlichen Hack) - es ist eine absolut erbärmliche Entschuldigung für einen Server, der "XML unterstützt" und das einfache Shreddern und den Zugriff viel komplizierter macht als nötig . –

3

Die Antwort von erikkallen absolut korrekt ist.

Wenn jedoch das Originaldokument/Schema geändert werden kann, besteht die Alternative darin, die Position/den Index in einem Attribut zu speichern. Ich verwende eine Mischung aus beiden Ansätzen, abhängig davon, wer der "Urheber" des XML ist und welche Art von Abfragen darauf ausgeführt werden müssen. Am Ende des Tages rue ich die meiste Verwendung von XML außer möglicherweise "dumm Speicher" in SQL Server und bin normalerweise glücklich, wenn ich es (XML) für normalisierte Tabellen ausgeben kann.

Glücklich Umgang mit den unerwähnten Einschränkungen von "Enterprise-Grade" -Produkten - die Wunder enden nie.

+0

+1 für Ihre "rue die meiste Verwendung von XML". Es ist wirklich nervig, wenn Sie Knoten auswählen, viel weniger sie aktualisieren. –

4

Sie die Stellung des XML durch eine x.nodes() Funktion wie so zurückgeführt bekommen:

row_number() over (order by (select 0)) 

Beispiels :

DECLARE @x XML 
SET @x = '<a><b><c>abc1</c><c>def1</c></b><b><c>abc2</c><c>def2</c></b></a>' 

SELECT 
    b.query('.'), 
    row_number() over (partition by 0 order by (select 0)) 
FROM 
    @x.nodes('/a/b') x(b) 
+1

Danke @Ben, ich habe neue Lösung row_number() über (Reihenfolge von (wählen Sie null)) –

+0

@nick_n_a, nett. – Ben

+0

@nick_n_a, basierend auf Ihrer Idee aktualisiert. – Ben

2

row_number() SQL Server tatsächlich akzeptiert Spalte eine XML-Knoten, die durch zu bestellen. In Kombination mit einem recursive CTE Sie können dies tun:

declare @Xml xml = 
'<value code="1"> 
    <value code="11"> 
     <value code="111"/> 
    </value> 
    <value code="12"> 
     <value code="121"> 
      <value code="1211"/> 
      <value code="1212"/> 
     </value> 
    </value> 
</value>' 

;with recur as (
    select 
     ordr  = row_number() over(order by x.ml), 
     parent_code = cast('' as varchar(255)), 
     code  = x.ml.value('@code', 'varchar(255)'), 
     children = x.ml.query('./value') 
    from @Xml.nodes('value') x(ml) 
    union all 
    select 
     ordr  = row_number() over(order by x.ml), 
     parent_code = recur.code, 
     code  = x.ml.value('@code', 'varchar(255)'), 
     children = x.ml.query('./value') 
    from recur 
    cross apply recur.children.nodes('value') x(ml) 
) 
select * 
from recur 
where parent_code = '121' 
order by ordr 

Als Nebenwirkung, können Sie dies tun, und es werde tun, was Sie erwarten:

select x.ml.query('.') 
from @Xml.nodes('value/value')x(ml) 
order by row_number() over (order by x.ml) 

Warum, wenn das funktioniert, Sie kann nicht einfach order by x.ml direkt ohne row_number() over ist über mich hinaus.

0

Ich sehe Antwort von @ Ben und ... erhalten neue sollution

row_number() over (order by (select null)) 

als

SELECT value.value('@code', 'varchar(20)') code, 
    value.value('../@code', 'varchar(20)') parent, 
    row_number() over (order by (select null)) 
    FROM @xml.nodes('/root//value') n(value) 
Verwandte Themen