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?
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.
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.
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 .
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:
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.../*
expression selects all siblings (children of the parent) of the current node.[. << $i]
predicate filters the list of siblings to those that precede (<<
) the current node ($i
).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.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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With