rank over shredded xml
Given the following sample of XML and the select
statement that shreds the xml into a relation, what I need is the second column of the select
to be the ordinal of the category (ie 1 for the directions and 2 for the colours in this case).
Note: The lite开发者_如何学Pythonral value 'rank()' in the select is left a placeholder. I was poking around with using the rank
, but with no success.
declare @x xml
set @x = '
<root>
<category>
<item value="north"/>
<item value="south"/>
<item value="east"/>
<item value="west"/>
</category>
<category>
<item value="red"/>
<item value="green"/>
<item value="blue"/>
</category>
</root>'
select c.value('./@value', 'varchar(10)') as "ItemValue",
'rank()' as "CategoryNumber"
from @x.nodes('//item') as t(c)
Jacob Sebastian also has an interesting solution presented in his blog post:
XQuery Lab 23 - Retrieving values and position of elements
With Jacob's suggestion, I can rewrite your query to be:
SELECT
x.value('@value','VARCHAR(10)') AS 'ItemValue',
p.number as 'CategoryNumber'
FROM
master..spt_values p
CROSS APPLY
@x.nodes('/root/category[position()=sql:column("number")]/item') n(x)
WHERE
p.type = 'p'
and I get the desired output:
ItemValue CategoryNumber
--------- --------------
north 1
south 1
east 1
west 1
red 2
green 2
blue 2
Unfortunately, none of the more obvious solutions like the position()
or fn:id()
functions seem to a) work in SQL Server or b) be supported in SQL Server at all :-(
Hope this helps
Marc
Maybe like this: you get first element of each category and use it as an id.
This:
select c.value('./@value', 'varchar(10)') as "ItemValue",
c.value('../item[1]/@value', 'varchar(10)') as "CategoryNumber"
from @x.nodes('//item') as t(c)
Returns:
Item Value | CategoryNumber
---------------------------
north | north
south | north
east | north
west | north
red | red
green | red
blue | red
And then just
select c.value('./@value', 'varchar(10)') as "ItemValue",
RANK() OVER (ORDER BY c.value('../item[1]/@value', 'varchar(10)')) as "CategoryNumber"
from @x.nodes('//item') as t(c)
It however returns:
Item Value | CategoryNumber
---------------------------
north | 1
south | 1
east | 1
west | 1
red | 5
green | 5
blue | 5
But it is still step ahead.
You cannot use position()
to produce output (why??), but you can use it as XPath filters:
with numbers (n) as (
select 1
union all select 2
union all select 3
union all select 4
union all select 5)
select i.x.value('@value', 'varchar(10)') as [ItemValue],
n.n as [rank]
from numbers n
cross apply @x.nodes('/root/category[position()=sql:column("n.n")]') as c(x)
cross apply c.x.nodes('item') as i(x);
You can use a real numbers table for higher ranks. For very large number of categories in a single document is not going to be efficient, but for moderate numbers (tens, hundreds) will work just fine.
Similar to Lukasz answer, I was able achieve the desired result with:
SELECT
I.Item_Instance.value('@value','VARCHAR(10)') AS Item_Value,
DENSE_RANK() OVER (ORDER BY C.Category_Instance) AS Category_Ordinal
FROM @x.nodes('/root') AS R(Root_Instance)
CROSS APPLY R.Root_Instance.nodes('category') AS C(Category_Instance)
CROSS APPLY C.Category_Instance.nodes('item') AS I(Item_Instance);
It returns:
Item_Value Category_Ordinal
---------- --------------------
north 1
south 1
east 1
west 1
red 2
green 2
blue 2
精彩评论