【问题标题】:TSQL\XPATH - Getting Proper XPath-Index of element on CROSS APPLY node(child)TSQL\XPATH - 在 CROSS APPLY 节点(子)上获取元素的正确 XPath 索引
【发布时间】:2017-09-20 16:20:37
【问题描述】:

使用 xpath child.value('for $i in . return count(../../*[. << $i])', 'int') 将通过计算父节点中出现在当前节点之前的相邻节点的数量,为我提供 XML 中节点的索引。这很好用,但我需要按 NODE-NAME 分组\重置索引计数。

(在下面的示例中,我将索引作为 varchar 查询,以便将字符串与其内容连接起来)

DECLARE @x XML = '
<Vitals>
    <BP>
        <Value>120/80</Value>
        <Time>02:00:05</Time>
    </BP>
    <BP>
        <Value>140/90</Value>
        <Time>02:10:01</Time>
    </BP>
    <BP>
        <Value>120/80</Value>
        <Time>02:15:05</Time>
    </BP>
    <HR>
        <Value>80</Value>
        <Time>02:00:12</Time>
    </HR>
    <HR>
        <Value>84</Value>
        <Time>02:10:12</Time>
    </HR>
</Vitals>'

SELECT '/' + child.value('local-name(../..)', 'varchar(max)') + '/' + child.value('local-name(..)', 'varchar(max)') + '[' + child.value('for $i in . return count(../../*[. << $i])', 'varchar(max)') + ']' AS NodePath
    ,child.value('local-name(.)', 'varchar(max)') AS NodeName
    ,child.value('.', 'varchar(max)') AS NodeValue
FROM (
    SELECT @x AS import_data
    ) i
CROSS APPLY i.import_data.nodes('/Vitals/*/*') AS nodes(child)
WHERE LEN(cast(child.query('./*') AS VARCHAR(max))) = 0
ORDER BY child.value('for $i in . return count(../../*[. << $i])', 'int')

返回:

/Vitals/BP[1]   Value   120/80
/Vitals/BP[1]   Time    02:00:05
/Vitals/BP[2]   Value   140/90
/Vitals/BP[2]   Time    02:10:01
/Vitals/BP[3]   Value   120/80 
/Vitals/BP[3]   Time    02:15:05
/Vitals/HR[4]   Value   80 
/Vitals/HR[4]   Time    02:00:12
/Vitals/HR[5]   Value   84 
/Vitals/HR[5]   Time    02:10:12

如您所见,BP[x] 索引是正确的,但是当节点名称从“BP”更改为“HR”时,索引会继续增加,而HR[x] 索引是错误的。我需要它来重置索引以获取元素的 PROPER XPATH 索引(同名元素的索引),而不仅仅是父元素的索引。我需要我的输出看起来像这样:

/Vitals/BP[1]   Value   120/80
/Vitals/BP[1]   Time    02:00:05
/Vitals/BP[2]   Value   140/90
/Vitals/BP[2]   Time    02:10:01
/Vitals/BP[3]   Value   120/80 
/Vitals/BP[3]   Time    02:15:05
/Vitals/HR[1]   Value   80 
/Vitals/HR[1]   Time    02:00:12
/Vitals/HR[2]   Value   84 
/Vitals/HR[2]   Time    02:10:12

有没有更好的方法来获得我正在寻找的东西?如何从我的节点获取正确的 XPATH 索引?

【问题讨论】:

    标签: sql sql-server xml tsql xpath


    【解决方案1】:

    注意+ cast( (row_number() over ...

    子节点的动态数量更新示例

    Select A.NodePath
          ,B.*
     From (
            Select NodePath  = '/'+lvl1.n.value('local-name(.)', 'varchar(max)') 
                              +'/'+lvl2.n.value('local-name(.)', 'varchar(max)')
                              +'['
                              + cast(Row_Number() over (Partition By  lvl2.n.value('local-name(.)', 'varchar(max)') Order By (Select null)) as varchar(25))
                              +']'
                  ,XMLData   = lvl2.n.query('*') 
             From  @X.nodes('Vitals')      lvl1(n)
             Cross Apply lvl1.n.nodes('*') lvl2(n)
          ) A 
     Cross Apply (
                    Select NodeName =lvl1.n.value('local-name(.)', 'varchar(max)') 
                          ,ItemValue=lvl1.n.value('(.)', 'varchar(max)') 
                     From  A.XMLData.nodes('*') lvl1(n)
                 ) B  
    

    退货

    NodePath        NodeName    NodeValue
    /Vitals/BP[1]   Value       120/80
    /Vitals/BP[1]   Time        02:00:05
    /Vitals/BP[2]   Value       140/90
    /Vitals/BP[2]   Time        02:10:01
    /Vitals/BP[3]   Value       120/80
    /Vitals/BP[3]   Time        02:15:05
    /Vitals/HR[1]   Value       80
    /Vitals/HR[1]   Time        02:00:12
    /Vitals/HR[2]   Value       84
    /Vitals/HR[2]   Time        02:10:12
    

    【讨论】:

    • @TaterJuice 刚刚添加了一个按子本地名称分区的 row_number()。如果删除除以 2,您将看到实际的行号。请记住,这适用于两对。如果你有 3 组,那么它将是 / 3
    • @TaterJuice 是的。我指的是价值和时间,而不是 BP 和 HR
    • @TaterJuice 窗口函数是无价的!非常值得你花时间适应他们:)
    • @TaterJuice 节点是否一致? 2个还是3个?
    • @TaterJuice 看看我更新的答案。抱歉耽搁了……妻子需要一只手来移动鹦鹉笼
    【解决方案2】:

    非常感谢约翰在回答中的辛勤工作。他的答案按原样工作(根据他的测试,速度更快[见他的评论,见下文]),但我想出了一种不太复杂的方法来获取索引!

    诀窍是在 xpath 中的 count() 函数期间将 local-name(.) 与节点的本地名称 $i 进行比较,如下所示:

    for $i in . return count(../../*[. << $i and local-name(.) = local-name($i/..)])
    

    现在完整的代码如下所示:

    DECLARE @x XML = '
        <Vitals>
            <BP>
                <Value>120/80</Value>
                <Time>02:00:05</Time>
            </BP>
            <BP>
                <Value>140/90</Value>
                <Time>02:10:01</Time>
            </BP>
            <BP>
                <Value>120/80</Value>
                <Time>02:15:05</Time>
            </BP>
            <HR>
                <Value>80</Value>
                <Time>02:00:12</Time>
                <Signature>twig</Signature>
            </HR>
            <HR>
                <Value>84</Value>
                <Time>02:10:12</Time>
                <Signature>twig</Signature>
            </HR>
        </Vitals>'
    

    SELECT '/' + child.value('local-name(../..)', 'varchar(max)') + '/' + child.value('local-name(..)', 'varchar(max)') + '[' + child.value('for $i in . return count(../../*[. << $i and local-name(.) = local-name($i/..)])', 'varchar(max)') + ']' AS NodePath
        ,child.value('local-name(.)', 'varchar(max)') AS NodeName
        ,child.value('.', 'varchar(max)') AS NodeValue
    FROM (
        SELECT @x AS import_data
        ) i
    CROSS APPLY i.import_data.nodes('/Vitals/*/*') AS nodes(child)
    WHERE LEN(cast(child.query('./*') AS VARCHAR(max))) = 0
    

    并按预期输出:

    /Vitals/BP[1] 值 120/80

    /Vitals/BP[1] 时间 02:00:05

    /Vitals/BP[2] 值 140/90

    /Vitals/BP[2] 时间 02:10:01

    /Vitals/BP[3] 值 120/80

    /Vitals/BP[3] 时间 02:15:05

    /Vitals/HR[1] 值 80

    /Vitals/HR[1] 时间 02:00:12

    /Vitals/HR[1] 签名树枝

    /Vitals/HR[2] 值 84

    /Vitals/HR[2] 时间 02:10:12

    /Vitals/HR[2] 签名树枝

    【讨论】:

    • 干得好!您的方法引起了我的兴趣,因此我必须对其进行测试。每次运行 15 次后,(我很高兴)我的方法快了 11% 到 24%(平均 15.353% 或 7.21 毫秒)。我不清楚 XML 的所有内部工作原理,但我怀疑降级发生在计数中。无论哪种方式,我什至从未考虑过计数,并且肯定会将其保存在我的工具箱中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-17
    • 2015-03-30
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    相关资源
    最近更新 更多