2009-07-15 11 views
15

Come trovare l'ordine dei nodi in un documento XML?Ricerca dell'ordine nodo nel documento XML in SQL Server

Quello che ho è un documento come questo:

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

e sto cercando di ottenere questa cosa in una tabella definita come

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

Preservare l'ordine dei valori dal XML documento (non possono essere ordinati dal loro codice). Voglio essere in grado di dire

SELECT code 
FROM values 
WHERE parent_code = 121 
ORDER BY ord 

ed i risultati dovrebbero, in modo deterministico, essere

code 
1211 
1212 

ho cercato

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 

Ma non accetta la funzione position() (' position() 'può essere utilizzato solo all'interno di un predicato o selettore XPath).

Immagino sia possibile in qualche modo, ma come?

+0

Hai profondità finita di nodi? Altrimenti, sarà un dolore. E per confermare: non puoi fare affidamento sui codici? – gbn

+0

... e quale output vuoi dall'xml? – gbn

+0

Ho aggiornato la domanda per fornire ulteriori informazioni. E no, c'è una profondità infinita. – erikkallen

risposta

31

È possibile emulare la funzione position() contando il numero di nodi fratello precedente ogni nodo:

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) 

Ecco il set di risultati:

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 

Come funziona:

  • La clausola for $i in . definisce una variabile denominata $i che contiene il nodo corrente (.). Questo è fondamentalmente un trucco per aggirare la mancanza di XQuery di una funzione current() simile a XSLT.
  • L'espressione ../* seleziona tutti i fratelli (figli del genitore) del nodo corrente.
  • Il [. << $i] predicato filtra l'elenco di fratelli a quelli che precedono (<<) nodo corrente ($i).
  • Noi count() il numero di fratelli precedenti e quindi aggiungere 1 per ottenere la posizione. In questo modo il primo nodo (che ha fratelli precedenti) viene assegnata una posizione di 1.
+2

Ho usato questo codice su un file XML piuttosto grande e perché 'per $ i in. return count (../* [. << $ i]) + 1' parte attraversa tutti i nodi "fratelli" che precedono ogni nodo questo ha richiesto per sempre (lo lasciamo funzionare mentre si va a casa, si è schiantato il giorno successivo). Quindi sii avvisato che questo codice ha un'efficienza O (n^2). – funkwurm

2

In base al documento this e questo connect entry non è possibile, ma la voce Connetti contiene due soluzioni alternative.

lo faccio come questo:

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

Ogni volta che provo a vedere se c'è un buon modo per farlo, ho sempre voglia di piangere. È l'unico modo che ho trovato (in realtà, io uso una tabella di numeri, ma lo stesso brutto attacco) - è una scusa assolutamente patetica per un server che "supporta XML" e semplifica la frantumazione e l'accesso molto più complicato di quanto debba essere . –

3

La risposta da erikkallen è assolutamente corretto.

Tuttavia, se il documento/schema originale può essere modificato, un'alternativa è quella di memorizzare la posizione/indice in un attributo. Uso un mix di entrambi gli approcci, a seconda di chi sia il "creatore" dell'XML e del tipo di query che devono essere eseguite su di esso. Alla fine della giornata, mi riferisco soprattutto all'XML, ad eccezione forse di "memoria stupida" in SQL Server e di solito sono contento quando posso scaricarlo (XML) per tabelle normalizzate.

Buon rapporto con le limitazioni non menzionate dei prodotti "di livello enterprise": le meraviglie non finiscono mai.

+0

+1 per la tua "maggior parte dell'uso di XML". Fa davvero schifo quando devi selezionare i nodi molto meno aggiornarli. –

4

È possibile ottenere la posizione del XML restituito da una funzione x.nodes() modo:

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

Ad esempio :

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

Grazie a @Ben, ho nuovo sollution row_number() over (ordina per (seleziona null)) –

+0

@nick_n_a, nice. – Ben

+0

@nick_n_a, aggiornato in base alla tua idea. – Ben

2

SQL di row_number() accetta in realtà una colonna XML-nodi di ordinare dal server. Combinato con un recursive CTE si può fare questo:

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 

Per inciso, si può fare questo e lo farà cosa vi aspettate:

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

Perché, se questo funziona, è non può solo order by x.ml direttamente senza row_number() over è oltre me.

0

vedo risposta da @ Ben e ... ottenere nuovi sollution

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

come

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) 
Problemi correlati