通过引用当前上下文节点过滤节点()XQuery

时间:2014-08-02 15:40:10

标签: xml tsql xquery-sql

是否可以在nodes()的XQuery语句中引用当前上下文节点?

实施例

假设我将XML数据排序在表/表变量中,其中每个文档的一个部分通过属性引用另一个部分中的元素。 (在下面,人和位置通过ID关联。)

DECLARE @Data TABLE (
    ID INT NOT NULL PRIMARY KEY,
    XmlData XML NOT NULL
)

INSERT INTO @Data
    VALUES (1, '<Root>
    <People>
        <Person id="1">Frank</Person>
        <Person id="2">Joe</Person>
    </People>
    <Positions>
        <Position assignedToPerson="1">Engineer</Position>
        <Position assignedToPerson="2">Manager</Position>
    </Positions>
    </Root>'),
    (2, '<Root>
    <People>
        <Person id="5">Bob</Person>
        <Person id="6">Sam</Person>
    </People>
    <Positions>
        <Position assignedToPerson="6">Mechanic</Position>
        <Position assignedToPerson="5">Accountant</Position>
    </Positions>
</Root>')

人员位置配对的结果集可以这样产生:

SELECT 
   PersonID = person.value('@id', 'NVARCHAR(50)'),
   Name = person.value('.', 'NVARCHAR(50)'),
   Position = position.value('.', 'NVARCHAR(50)')
FROM @Data
    CROSS APPLY XmlData.nodes('/Root/People/Person') People(person)
    CROSS APPLY person.nodes('/Root/Positions/Position') Positions(position)
WHERE person.value('@id', 'NVARCHAR(50)')= position.value('@assignedToPerson[1]','NVARCHAR(50)')
+----------+-------+------------+
| PersonID | Name  |  Position  |
+----------+-------+------------+
|        1 | Frank | Engineer   |
|        2 | Joe   | Manager    |
|        5 | Bob   | Accountant |
|        6 | Sam   | Mechanic   |
+----------+-------+------------+

问题

第二个CROSS APPLY在每次调用时将相关XML文档中定义的每个位置分成自己的行。结果是文档中每个人与文档中定义的每个位置配对。将结果集过滤到相关的人员 - 位置对发生在WHERE子句中。

目标

我想通过在第二个XQuery中引用People(person)的上下文节点来消除将所有人匹配到每个位置 - 就像这样:

SELECT 
   PersonID = person.value('@id', 'NVARCHAR(50)'),
   Name = person.value('.', 'NVARCHAR(50)'),
   Position = position.value('.', 'NVARCHAR(50)')
FROM @Data
    CROSS APPLY XmlData.nodes('/Root/People/Person') People(person)
    CROSS APPLY person.nodes('/Root/Positions/Position[@assignedToPerson={{**reference to @ID of context node**}}]') Positions(position)

我可以在第二个nodes()的XQuery中引用第一个CROSS APPLY上下文节点吗?

(使用JOIN-based approach不起作用,因为上述数据来自表,而不是XML变量。)

1 个答案:

答案 0 :(得分:1)

基本上,你想加入人和职位。

(1)如果那些存储在表格中的人和位置的数据可以很容易地完成。这也意味着,我将此信息存储在单个列中而不是单个XML列中。

(2)如果由于某些原因你不能这样做,那么你可以重新设计XML:

<Root>
  <People>
    <Person id="1" name="Frank" position="Engineer" />
    <Person id="2" name="Joe" position="Manager" />
  </People>
</Root>

(示例:

UPDATE  x
SET     XmlData = NewXmlData
FROM (
    SELECT  d.ID, d.XmlData,  d.XmlData.query('
        <Root>
        <People>
        {for $per in (/Root/People/Person)
            for $pos in (/Root/Positions/Position[@assignedToPerson = $per/@id])
                return  
                    <Person id="{$per/@id}" name="{$per/text()}" position="{$pos/text()}"/>}
        </People>
        </Root>
        ') AS NewXmlData
    FROM    @Data d
) x 

SELECT  d.ID,
        x.XmlPerson.value('(@name)[1]', 'NVARCHAR(50)') AS name,
        x.XmlPerson.value('(@position)[1]', 'NVARCHAR(50)') AS position
FROM    @Data d
CROSS APPLY d.XmlData.nodes('/Root/People/Person') x(XmlPerson)

<Root>
  <People>
    <Person id="1" name="Frank">
      <Positions>
        <Position>Engineer</Position>
      </Positions>
    </Person>
    <Person id="2" name="Joe">
      <Positions>
        <Position>Manager</Position>
      </Positions>
    </Person>
  </People>
</Root>

(不完整的例子:

SELECT  d.XmlData.query('
<Root>
    <People>
    {for $per in (/Root/People/Person)
        return
            <Person id="{$per/@id}" name="{$per/text()}">
                <Positions>
                {for $pos in (/Root/Positions/Position[@assignedToPerson = $per/@id])
                    return <Position>{string($pos/text()[1])}</Position>} 
                </Positions>
            </Person> 
    }
    </People>
</Root>') AS NewXmlData
FROM    @Data d

(3)如果由于某些原因,您无法做到这一点,并且如果您想找到更好的解决方案(从性能的角度来看),那么您可以使用以下解决方案之一:

PRINT 'Solution #1'
SELECT  Person.Person_id, 
        Person.Person_name,
        Position_name = Person.XmlData.value('(/Root/Positions/Position[@assignedToPerson = sql:column("Person_id")]/text())[1]', 'NVARCHAR(50)')
FROM (
    SELECT  Person_id = x.XmlPerson.value('(@id)[1]', 'INT'),
            Person_name = x.XmlPerson.value('(text())[1]', 'NVARCHAR(50)'),
            d.XmlData
    FROM    @Data d
    CROSS APPLY d.XmlData.nodes('/Root/People/Person') x(XmlPerson)
) Person 

PRINT 'Solution #2'
SELECT  Person.Person_id, 
        Person.Person_name,
        Position.Position_name
FROM (
    SELECT  Person_id = x.XmlPerson.value('(@id)[1]', 'INT'),
            Person_name = x.XmlPerson.value('(text())[1]', 'NVARCHAR(50)'),
            d.XmlData
    FROM    @Data d
    CROSS APPLY d.XmlData.nodes('/Root/People/Person') x(XmlPerson)
) Person INNER /*HASH*/ JOIN (
    SELECT  Position_assignedToPerson = x.XmlPerson.value('(@assignedToPerson)[1]', 'INT'),
            Position_name = x.XmlPerson.value('(text())[1]', 'NVARCHAR(50)')
    FROM    @Data d
    CROSS APPLY d.XmlData.nodes('/Root/Positions/Position') x(XmlPerson)
) Position ON Person.Person_id = Position.Position_assignedToPerson

PRINT 'Solution #3'
SELECT  Person.Person_id, 
        Person.Person_name,
        Position_name = y.XmlPosition.value('(text())[1]', 'NVARCHAR(50)')
FROM (
    SELECT  Person_id = x.XmlPerson.value('(@id)[1]', 'INT'),
            Person_name = x.XmlPerson.value('(text())[1]', 'NVARCHAR(50)'),
            d.XmlData
    FROM    @Data d
    CROSS APPLY d.XmlData.nodes('/Root/People/Person') x(XmlPerson)
) Person
CROSS APPLY Person.XmlData.nodes('/Root/Positions/Position[@assignedToPerson = sql:column("Person_id")]') y(XmlPosition);

注意:如果这不是一次性任务,那么我将使用(1)。

注意#2:问题的唯一解决方案(或多或少)“是否可以在nodes()的XQuery语句中引用当前上下文节点?”是(2)的例子。