Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finding node order in XML document in SQL Server

How can I find the order of nodes in an XML document?

What I have is a document like this:

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

and I'm trying to get this thing into a table defined like

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

Preserving the order of the values from the XML document (they can't be ordered by their code). I want to be able to say

SELECT code 
FROM values 
WHERE parent_code = 121 
ORDER BY ord

and the results should, deterministically, be

code
1211
1212

I have tried

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

But it doesn't accept the position() function ('position()' can only be used within a predicate or XPath selector).

I guess it's possible some way, but how?

like image 709
erikkallen Avatar asked Jul 15 '09 21:07

erikkallen


People also ask

How do I find specific nodes in XML?

To find nodes in an XML file you can use XPath expressions. Method XmlNode. SelectNodes returns a list of nodes selected by the XPath string.

How do I select a specific XML node in SQL Server?

You should use the query() Method if you want to get a part of your XML. If you want the value from a specific node you should use value() Method. Update: If you want to shred your XML to multiple rows you use nodes() Method.

Are XML nodes ordered?

Figure 10.1: An XML document. An XML document is an ordered, labeled tree. Each node of the tree is an XML element and is written with an opening and closing tag . An element can have one or more XML attributes .


2 Answers

You can emulate the position() function by counting the number of sibling nodes preceding each node:

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)

Here is the result set:

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

How it works:

  • The for $i in . clause defines a variable named $i that contains the current node (.). This is basically a hack to work around XQuery's lack of an XSLT-like current() function.
  • The ../* expression selects all siblings (children of the parent) of the current node.
  • The [. << $i] predicate filters the list of siblings to those that precede (<<) the current node ($i).
  • We count() the number of preceding siblings and then add 1 to get the position. That way the first node (which has no preceding siblings) is assigned a position of 1.
like image 128
Michael Liu Avatar answered Sep 28 '22 07:09

Michael Liu


You can get the position of the xml returned by a x.nodes() function like so:

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

For example:

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)
like image 37
Ben Avatar answered Sep 28 '22 06:09

Ben