ранжировать измельченный XML - PullRequest
3 голосов
/ 07 октября 2009

Учитывая следующий образец XML и оператор select, который разбивает xml на отношение, мне нужно , чтобы второй столбец select был порядковым номером категории (т.е. 1 для направлений и 2 для цветов в этом случае).

Примечание. Литеральное значение rank () в выборке остается заполнителем. Я возился с использованием rank, но безуспешно.

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)

Ответы [ 4 ]

2 голосов
/ 08 октября 2009

У Джейкоба Себастьяна также есть интересное решение, представленное в его сообщении в блоге:

XQuery Lab 23 - Получение значений и положения элементов

С предложением Джейкоба я могу переписать ваш запрос следующим образом:

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'

и я получаю желаемый результат:

ItemValue   CategoryNumber
---------   --------------
north           1
south           1
east            1
west            1
red             2
green           2
blue            2

К сожалению, ни одно из более очевидных решений, таких как функции position() или fn:id(), похоже, а) не работает в SQL Server или б) вообще не поддерживается в SQL Server: - (

Надеюсь, это поможет

Марк

1 голос
/ 07 октября 2009

Вы не можете использовать position() для вывода (почему ??), но вы можете использовать его как фильтры XPath:

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

Вы можете использовать таблицу действительных чисел для более высоких рангов. Для очень большого количества категорий в одном документе это не будет эффективно, но для умеренных чисел (десятки, сотни) будет работать просто отлично.

1 голос
/ 07 октября 2009

Может быть так: вы получаете первый элемент каждой категории и используете его в качестве идентификатора.

Это:

select c.value('./@value', 'varchar(10)') as "ItemValue", 
    c.value('../item[1]/@value', 'varchar(10)') as "CategoryNumber"
from @x.nodes('//item') as t(c)

Возвращает:

Item Value | CategoryNumber
---------------------------
north      | north
south      | north
east       | north
west       | north
red        | red
green      | red
blue       | red

А потом просто

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)

Однако возвращается:

Item Value | CategoryNumber
---------------------------
north      | 1
south      | 1
east       | 1
west       | 1
red        | 5
green      | 5
blue       | 5

Но это все еще на шаг впереди.

0 голосов
/ 01 июня 2017

Подобно ответу Лукаша, я смог достичь желаемого результата с:

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

Возвращает:

Item_Value Category_Ordinal
---------- --------------------
north      1
south      1
east       1
west       1
red        2
green      2
blue       2
...