使用XPATH / XQUERY为具有属性X的元素选择所有值

时间:2013-02-27 16:30:24

标签: sql sql-server xpath xquery

我对XPath和XQuery相对较新。我正在使用存储在名为tblXML的表中的XML。 XML由caseID存储在此表中。对于此示例,此值是唯一的。 XML本身存储在标有Data的列中。

如果您select * from tblXML where caseID = '12345',您会看到:

caseID        data
------        ------
12345         <root><patient><diagnosis preferred_name="diagnosis" tier="1">Melanoma</diagnosis> (XML file stored as blob in database)

如果您要深入研究XML,它看起来就像这样。例如,这是data'12345'的caseID列中的XML:

<root>
    <patient>
        <diagnosis preferred_name="diagnosis" tier="1">Melanoma</diagnosis>
        <month_of_birth preferred_name="month_of_birth" tier="1">02</month_of_birth>
        <day_of_birth preferred_name="day_of_birth" tier="2">01</day_of_birth>
        <year_of_birth preferred_name="year_of_birth" tier="1">1960</year_of_birth>
        <new_tumor_events>
            <month_of_nte preferred_name="month_of_nte" tier="1">12</month_of_nte>
            <day_of_nte preferred_name="day_of_nte" tier="2">30</day_of_nte>
            <year_of_nte preferred_name="year_of_nte" tier="1">1994</year_of_nte>
        </new_tumor_events>
        <follow_ups>
            <follow_up>
                <month_of_fu preferred_name="month_of_fu" tier="1">12</month_of_fu>
                <day_of_fu preferred_name="day_of_fu" tier="2">31</day_of_fu>
                <year_of_fu preferred_name="year_of_fu" tier="1">1995</year_of_fu>
            </follow_up>
            <follow_up>
                <month_of_fu preferred_name="month_of_fu" tier="1">12</month_of_fu>
                <day_of_fu preferred_name="day_of_fu" tier="2">31</day_of_fu>
                <year_of_fu preferred_name="year_of_fu" tier="1">1996</year_of_fu>
            </follow_up>
        </follow_ups>
    </patient>
</root>

目标: 我正在尝试为@Tier属性为"1"的XML文档的所有级别/节点选择所有元素的所有值。我想看到每个值都在它自己的行中输出一个名为'Value'的列,如下所示:

caseID     Value
------     ------
12345      Melanoma
12345      02
12345      1960
12345      12
12345      1994
12345      12
12345      1995
12345      12
12345      1996

这里的SQL类似于我正在尝试编写的内容:

DECLARE @tier int 
SET @tier = '1' --To search by tier explicitly via variable

    select 
        x.caseID,
    bar.value('(//*[@tier = sql:variable("@tier")])[1]','varchar(max)') as value
from tblXML as x
    CROSS APPLY data.nodes('//*:patient') AS foo(bar)
where
    x.caseID = '12345'
    and x.data.value('(//@tier)[1]', 'varchar(3)') = '1'

这给了我......

case ID        value
------         ------  
12345          Melanoma

...但不是我正在寻找的其他行。

我认为我的困惑集中在单例[1]强加的约束和我的CROSS APPLY语句中的上下文节点。我不确定如何在第1层的文档中获取所有内容,而不仅仅是查询找到的第1层的第一个实例。

对于这个例子,我知道我的CROSS APPLY语句中的上下文节点集中在XML的patient部分,但我希望能够在XML树的所有级别上下搜索。我知道// @Tier允许我在文档的所有级别进行搜索,但我的上下文节点似乎在某种程度上推动/约束我可以搜索的文档的深度。

我的理解是我可以深入研究XML(也许是follow_up?),然后从上到下进行搜索。但是,最深的节点将从caseID更改为caseID和XML文档到XML文档。有没有办法将我的CROSS APPLY语句通配到给定XML文档中最深的节点,然后在该文档中查找@Tier的任何出现,然后检索其对应的值?

我意识到这种方法缺乏优雅,并且不具备高效性。到目前为止,我还没有找到明确的文档给我正在寻找的结果。

提前感谢您的帮助。

编辑:我应该指定这个SQL将是SQL Server中表值函数的核心。

4 个答案:

答案 0 :(得分:2)

定义一个键:

<xsl:key name="t" match="*[@tier]" use="@tier"/>

并使用

查找所有tier = 1个元素
<xsl:for-each select="key('t', '1')">
  ....
</xsl:for-each>

答案 1 :(得分:1)

尝试删除您选择中的单身人士:

DECLARE @tier int 
SET @tier = '1' --To search by tier explicitly via variable
    select 
        x.caseID,
        bar.value('/string()','varchar(max)')
from tblXML as x
    CROSS APPLY data.nodes('//*[@tier = sql:variable("@tier")]') AS foo(bar)
where
    x.caseID = '12345'

select中的xpath已根据匹配的属性值过滤节点,因此您无需在where约束中再次执行此操作。 xpath谓词类似于where。

答案 2 :(得分:0)

试试这个:

SELECT  
    x.caseID,
    bar.value('.','varchar(max)') as value
FROM tblXML as x
    CROSS APPLY data.nodes('//*[@tier=1]') AS foo(bar)

答案 3 :(得分:0)

您基本上可以从CROSS APPLY语句执行层查找,并在select('。'作为第一个参数)中获取root的值。

DECLARE @s XML;
SET @s='<root>
    <patient>
        <diagnosis preferred_name="diagnosis" tier="1">Melanoma</diagnosis>
        <month_of_birth preferred_name="month_of_birth" tier="1">02</month_of_birth>
        <day_of_birth preferred_name="day_of_birth" tier="2">01</day_of_birth>
        <year_of_birth preferred_name="year_of_birth" tier="1">1960</year_of_birth>
        <new_tumor_events>
            <month_of_nte preferred_name="month_of_nte" tier="1">12</month_of_nte>
            <day_of_nte preferred_name="day_of_nte" tier="2">30</day_of_nte>
            <year_of_nte preferred_name="year_of_nte" tier="1">1994</year_of_nte>
        </new_tumor_events>
        <follow_ups>
            <follow_up>
                <month_of_fu preferred_name="month_of_fu" tier="1">12</month_of_fu>
                <day_of_fu preferred_name="day_of_fu" tier="2">31</day_of_fu>
                <year_of_fu preferred_name="year_of_fu" tier="1">1995</year_of_fu>
            </follow_up>
            <follow_up>
                <month_of_fu preferred_name="month_of_fu" tier="1">12</month_of_fu>
                <day_of_fu preferred_name="day_of_fu" tier="2">31</day_of_fu>
                <year_of_fu preferred_name="year_of_fu" tier="1">1996</year_of_fu>
            </follow_up>
        </follow_ups>
    </patient>
</root>';

DECLARE @tblXML TABLE(caseID INT PRIMARY KEY,data XML);
INSERT INTO @tblXML(caseID,data)VALUES(12345,@s);

DECLARE @tier INT;
SET @tier=1;

SELECT
    t.caseID,
    node.value('.','NVARCHAR(512)') AS value
FROM
    @tblXML AS t
    CROSS APPLY t.data.nodes('//*[@tier=sql:variable("@tier")]') AS t1n(node)
WHERE
    t.caseID=12345;