在SQL Server中查找XML文档中的节点顺序

eri*_*len 18 xml sql sql-server xquery

如何在XML文档中找到节点的顺序?

我所拥有的是这样的文件:

<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>
Run Code Online (Sandbox Code Playgroud)

而我正试图将这个东西放到一个定义为的表中

CREATE TABLE values(
    code int,
    parent_code int,
    ord int
)
Run Code Online (Sandbox Code Playgroud)

保留XML文档中值的顺序(它们不能按代码排序).我希望能够说出来

SELECT code 
FROM values 
WHERE parent_code = 121 
ORDER BY ord
Run Code Online (Sandbox Code Playgroud)

结果应该是确定的

code
1211
1212
Run Code Online (Sandbox Code Playgroud)

我试过了

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
Run Code Online (Sandbox Code Playgroud)

但它不接受该position()函数(' position()'只能在谓词或XPath选择器中使用).

我想这可能是某种方式,但如何?

Mic*_*Liu 34

您可以position()通过计算每个节点前面的兄弟节点的数量来模拟该函数:

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)
Run Code Online (Sandbox Code Playgroud)

这是结果集:

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
Run Code Online (Sandbox Code Playgroud)

这个怎么运作:

  • for $i in .子句定义了一个名为$i包含当前节点(.)的变量.这基本上是一个解决XQuery缺乏类似XSLT current()功能的黑客.
  • ../*表达式选择当前节点的所有兄弟节点(父节点的子节点).
  • 所述[. << $i]谓词过滤兄弟姐妹的那些先于列表(<<)当前节点($i).
  • 我们count()先前的兄弟姐妹的数量,然后加1来获得位置.这样,第一个节点(没有前面的兄弟节点)被分配了1的位置.

  • 我在一个相当大的XML文件上使用了这个代码,因为`for $ i in.返回计数(../*[.<< $ i])+ 1`部分遍历每个节点之前的所有"兄弟"节点,这是永远的(我们让它在回家时工作,第二天就崩溃了).因此,预先警告此代码具有O(n ^ 2)效率. (3认同)

Ben*_*Ben 5

您可以x.nodes()像这样获取函数返回的xml的位置:

row_number() over (order by (select 0))
Run Code Online (Sandbox Code Playgroud)

例如:

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)
Run Code Online (Sandbox Code Playgroud)

  • 无法保证订单会符合要求 (2认同)

fun*_*urm 5

SQL Server row_number()实际上接受一个xml-nodes列来排序.结合递归CTE,您可以这样做:

declare @Xml xml = 
'<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>'

;with recur as (
    select
        ordr        = row_number() over(order by x.ml),
        parent_code = cast('' as varchar(255)),
        code        = x.ml.value('@code', 'varchar(255)'),
        children    = x.ml.query('./value')
    from @Xml.nodes('value') x(ml)
    union all
    select
        ordr        = row_number() over(order by x.ml),
        parent_code = recur.code,
        code        = x.ml.value('@code', 'varchar(255)'),
        children    = x.ml.query('./value')
    from recur
    cross apply recur.children.nodes('value') x(ml)
)
select *
from recur
where parent_code = '121'
order by ordr
Run Code Online (Sandbox Code Playgroud)

顺便说一句,你可以这样做,它会做你期望的事情:

select x.ml.query('.')
from @Xml.nodes('value/value')x(ml)
order by row_number() over (order by x.ml)
Run Code Online (Sandbox Code Playgroud)

为什么,如果这有效,你不能order by x.ml直接没有row_number() over超越我.