Finding node order in XML document in SQL Server
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-likecurrent()
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.
Does the `nodes()` method keep the document order?
Yes, nodes()
generates a row set in document order. The operator used in the query plan to do this is the Table Valued Function XML Reader.
Table-valued Function XML Reader inputs an XML BLOB as a parameter and
produces a row set representing XML nodes in XML document order. Other
input parameters may restrict XML nodes returned to a subset of XML
document.
But a query without order by
has an undefined order so there are no guarantees.
One way to work around that is to use the id
generated by the table valued function in row_number() over()
clause and use the generated number in the order by.
select X.q
from
(
select T.c.query('.') as q,
row_number() over(order by T.c) as rn
from @xml.nodes('/Fruits/*') T(c)
) as X
order by X.rn
It is not possible to use T.c
in an order by
directly. Trying that will give you
Msg 493, Level 16, State 1, Line 19
The column 'c' that was returned from the nodes() method cannot be used directly. It can only be used with one of the four XML data type methods, exist(), nodes(), query(), and value(), or in IS NULL and IS NOT NULL checks.
The error did not mention that it should work with row_number
but it does and that could very well be bug that might get fixed so the code above will fail. But up until SQL Server 2012 it works just fine.
A way to get a guaranteed order without relying on the undocumented use of row_number
would be to use a table of numbers where you extract the nodes by position.
select T.c.query('.') as q
from Numbers as N
cross apply @xml.nodes('/Fruits/*[sql:column("N.Number")]') as T(c)
where N.Number between 1 and @xml.value('count(/Fruits/*)', 'int')
order by N.Number
Order of XML nodes from document preserved in insert?
There's no way to see it explicitly in an execution plan, but the id
column returned by the nodes()
method is a varbinary(900)
OrdPath, which does encapsulate the original xml document order.
The solution offered by Mikael Eriksson on the related question Does the `nodes()` method keep the document order? relies on the OrdPath to provide an ORDER BY
clause necessary to determine how identity values are assigned for the INSERT
.
A slightly more compact usage follows:
CREATE TABLE #T
(
ID integer IDENTITY,
Fruit nvarchar(10) NOT NULL
);
DECLARE @xml xml =
N'
<Fruits>
<Apple />
<Banana />
<Orange />
<Pear />
</Fruits>
';
INSERT #T
(Fruit)
SELECT
N.n.value('local-name(.)', 'nvarchar(10)')
FROM @xml.nodes('/Fruits/*') AS N (n)
ORDER BY
ROW_NUMBER() OVER (ORDER BY N.n);
SELECT
T.ID,
T.Fruit
FROM #T AS T
ORDER BY
T.ID;
db<>fiddle
Using the OrdPath this way is presently undocumented, but the technique is sound in principle:
- The OrdPath reflects document order.
- The
ROW_NUMBER
computes sequence values ordered by OrdPath*. - The
ORDER BY
clause uses the row number sequence. - Identity values are assigned to rows as per the
ORDER BY
.
To be clear, this holds even if parallelism is employed. As Mikael says, the dubious aspect is using id
in the ROW_NUMBER
since id
is not documented to be the OrdPath.
* The ordering is not shown in plans, but optimizer output using TF 8607 contains:
ScaOp_SeqFunc row_number order[CALC:QCOL: XML Reader with XPath filter.id ASC]
In SQL Server 2005, how can I store XML nodes in a table while preserving document order?
I'm currently doing the following, relying on the 'mp:id' to provide the ordering.
DECLARE @xmltext NVARCHAR(MAX)
SET @xmltext =
N'
<Queue>
<User name="Bob"/>
<User name="Jane"/>
<User name="Douglas"/>
<User name="Samantha"/>
</Queue>';
DECLARE @xmlObjectHandler INT;
-- Set up XML and the appropriate namespace(s) we'll be using
EXEC sp_xml_preparedocument @xmlObjectHandler OUTPUT, @xmltext;
DECLARE @TUserQueue TABLE (
Ordinal INT IDENTITY(1,1),
UserName NVARCHAR(200)
)
INSERT INTO @TUserQueue
SELECT
UserName
FROM
OPENXML(@xmlObjectHandler, '/Queue/User')
WITH (
NodeId INT '@mp:id',
UserName NVARCHAR(200) '@name'
) AS users
ORDER BY users.NodeId;
SELECT * FROM @TUserQueue
SQL Server: Row_Number() order by sort by xml data order
Following the previous answer
declare @islem xml = '<?xml version="1.0"?>
<ArrayOfDecimal xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<decimal>4000000</decimal>
<decimal>343</decimal>
<decimal>4000000</decimal>
</ArrayOfDecimal>';
with Numbers(Number) as(
-- quick inline tally
select 1 union all
select 2 union all
select 3
)
select T.X.value('text()[1]', 'nvarchar(100)') as RowLabel,
N.Number as RowNumber
from Numbers as N
cross apply @islem.nodes('ArrayOfDecimal/decimal[sql:column("N.Number")]') as T(X)
where N.Number between 1 and @islem.value('count(ArrayOfDecimal/decimal)', 'int');
You may wish to create a persistent Numbers
tally table instead of quick sample in the query.
Order attribute names in xml using t-sql
Not pretty, but this is where my thinking takes me.
dbFiddle
Example
DECLARE @xml XML = N'
<tt>
<cpost s="a" cena="0.0000" cpost_id="16385" flprt="1" moq="0" valuta_id="2" nmatr_id="14117" norg_id="1791" />
</tt>'
Declare @S varchar(max) = ''
Select @S = @S + concat(Item,'="',Value,'" ')
From (
Select Top 1000
Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From @XML.nodes('/tt/cpost') as A(r)
Cross Apply A.r.nodes('./@*') AS B(attr)
Order By attr.value('local-name(.)','varchar(100)')
) A
Select convert(xml,'<tt><cpost '+@S+'/></tt>')
Returns
<tt>
<cpost cena="0.0000" cpost_id="16385" flprt="1" moq="0" nmatr_id="14117" norg_id="1791" s="a" valuta_id="2" />
</tt>
EDIT - Added an In-Line Approach
DECLARE @xml XML = N'
<tt>
<cpost s="a" cena="0.0000" cpost_id="16385" flprt="1" moq="0" valuta_id="2" nmatr_id="14117" norg_id="1791" />
</tt>'
Select convert(xml,'<tt><cpost '+Stuff((Select ' ' +concat(Item,'="',Value,'" ')
From (
Select Top 1000
Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From @XML.nodes('/tt/cpost') as A(r)
Cross Apply A.r.nodes('./@*') AS B(attr)
Order By attr.value('local-name(.)','varchar(100)')
) A
For XML Path ('')),1,1,'') +'/></tt>')
SQL Server : FOR XML sorting control by attribute
If you want to reorder existing xml, you can use XQuery
:
declare @data xml = '
<test>
<tree abc="123"/>
<tree abc="789"/>
<tree-order abc="456"/>
</test>
'
select @data.query('<test>{for $i in test/* order by $i/@abc return $i}</test>')
Result:
<test>
<tree abc="123" />
<tree-order abc="456" />
<tree abc="789" />
</test>
sql fiddle demo
update:
To reorder multiple nodes, you can use XQuery like this:
select @data.query('
element Main {
for $j in Main/test
return element test {
for $i in $j/* order by $i/@abc return $i
}
}
')
sql fiddle demo
update 2
To order by integer values of attributes, use cast as <type>
:
select @data.query('
element Main {
for $j in Main/test
return element test {
for $i in $j/* order by $i/@abc cast as xs:integer?
return $i
}
}
')
sql fiddle demo
Use SQL Server to get all the data from XML nodes named the same
Try this :-
SELECT
author.value('./text()[1]', 'varchar(MAX)') AS 'Author'
FROM @MyXML.nodes('//refworks/reference/child::node()') AS ref(author)
where author.value('local-name(.)[1]', 'varchar(100)') ='a1'
child::node() represents an axis specifier which is child
and ::
is the axis separator.
For understanding child axis which is used to drill down in the node can be found in this MSDN document.
or manipulating xml data in sql server
Updated :-
A much simplier way You were on the right track .Specify the child node in the from clause for filtering the data
SELECT
author.value('(.)[1]', 'varchar(MAX)') AS 'Author'
FROM @MyXML.nodes('/refworks/reference/a1') AS ref(author)
SQL, Find node value in xml variable, if it exists insert additional nodes into xml variable
Check this:
declare @fields xml = '<F><ID>979</ID><ID>1000</ID><ID>989</ID></F>'
, @add xml = '<ID>992</ID><ID>993</ID>'
;
if @fields.exist('/F[1]/ID[text()="1000"]') = 1
set @fields.modify('insert sql:variable("@add") as last into /F[1]');
select @fields
How to query values from xml nodes?
SELECT b.BatchID,
x.XmlCol.value('(ReportHeader/OrganizationReportReferenceIdentifier)[1]','VARCHAR(100)') AS OrganizationReportReferenceIdentifier,
x.XmlCol.value('(ReportHeader/OrganizationNumber)[1]','VARCHAR(100)') AS OrganizationNumber
FROM Batches b
CROSS APPLY b.RawXml.nodes('/CasinoDisbursementReportXmlFile/CasinoDisbursementReport') x(XmlCol);
Demo: SQLFiddle
Related Topics
Why Are Aggregate Functions Not Allowed in Where Clause
Ora-00972 Identifier Is Too Long Alias Column Name
MySQL How to Insert into [Temp Table] from [Stored Procedure]
How Universal Is the Limit Statement in SQL
Parallel Unnest() and Sort Order in Postgresql
Local Temporary Table in Oracle 10 (For the Scope of Stored Procedure)
How to Report an Error from a SQL Server User-Defined Function
Sql: Two Select Statements in One Query
How to Compare 2 Rows from the Same Table (SQL Server)
How to Retrieve Field Names from Temporary Table (SQL Server 2008)
Order by Items Must Appear in the Select List If Select Distinct Is Specified
Oracle: Updating a Table Column Using Rownum in Conjunction with Order by Clause
Difference Between === Null and Isnull in Spark Datadrame
How to Transform Comma Separated Column into Multiples Rows in Db2
Postgresql 9.3: Dynamic Pivot Table
Get Previous and Next Row from Rows Selected with (Where) Conditions