This is a solution, where I'd claim it will work in all scenarios - although it is not guaranteed that the number bound to a <Department>
node via ROW_NUMBER()
will reflect its real position in each and any situation (see remarks and links below):
declare @myxml xml ='
<Departments>
<Department>
<Employee>
A
</Employee>
<Employee>
B
</Employee>
</Department>
<Department>
<Employee>
C
</Employee>
<Employee>
D
</Employee>
</Department>
</Departments>';
--The query will use a CTE to bind numbers to the first level and pass the inner node in whole. The final SELECT will use the passed over <Department>
node and pick its employees:
WITH NumberedDepartment AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS DepInx
,d.query(N'.') AS DepartmentNode
FROM @myxml.nodes(N'/Departments/Department') AS A(d)
)
SELECT DepInx
,e.value(N'text()[1]','nvarchar(max)') AS Employee
FROM NumberedDepartment
CROSS APPLY DepartmentNode.nodes(N'Department/Employee') AS B(e);
If you want to read about the guarantee of sort order you might read this thread. Especially readworthy: The chat below John Cappellettis answer. Over there I provide an approach using XQuery
, and one more approach using a tally / numbers table to pick the elements at their position. But this is rather complicated and slow.
UPDATE: An approach with a guaranteed sort order
This approach will create a tally table on-the-fly. If you have a numbers table this was even better...
The TOP
clause will limit this tally to the actual count of <Department>
nodes. Be sure to use a source table (I use master..spt_values
) which has at least as many rows as you might need.
The first apply will use .query()
together with sql:column()
to get the right department node for each number. The second apply will read the related employees.
WITH Tally(Nmbr) AS
(
SELECT TOP (SELECT @myxml.value(N'count(/Departments/Department)','int'))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values --just a pre-filled table with many rows
)
SELECT Nmbr
,e.value(N'text()[1]','nvarchar(max)') AS Employee
FROM Tally
OUTER APPLY(SELECT @myxml.query(N'/Departments/Department[sql:column("Nmbr")]')) AS A(ds)
OUTER APPLY ds.nodes(N'Department/Employee') AS B(e);