Сопоставление одного атрибута с другим с использованием XPath / XQuery в SQL Server 2008 - PullRequest
2 голосов
/ 01 декабря 2011

Рассмотрим XML и SQL:

declare @xml xml = '
<root>
    <person id="11272">
        <notes for="107">Some notes!</notes>
        <item id="107" selected="1" />
    </person>
    <person id="77812">
        <notes for="107"></notes>
        <notes for="119">Hello</notes>
        <item id="107" selected="0" />
        <item id="119" selected="1" />
    </person>
</root>'

select  Row.Person.value('data(../@id)', 'int') as person_id,
        Row.Person.value('data(@id)', 'int') as item_id,
        Row.Person.value('data(../notes[@for=data(@id)][1])', 'varchar(max)') as notes
from    @xml.nodes('/root/person/item') as Row(Person)

Я получаю:

person_id   item_id     notes
----------- ----------- -------
77812       107         NULL
77812       119         NULL
11272       107         NULL

Я хочу, чтобы столбец «Заметки» извлекался на основе атрибута @id текущего item. Если я заменю [@for=data(@id)] в селекторе на [@for=107], конечно, я получу значение Some notes! в последней записи. Возможно ли это сделать с помощью XPath / XQuery или я лаю здесь не то дерево? Я думаю, что проблема в том, что

XML немного неловко, да, но я не могу изменить его, я боюсь.

Я нашел одно решение, которое работает, но оно кажется ужасно тяжелым для чего-то подобного.

select  Item.Person.value('data(../@id)', 'int') as person_id,
        Item.Person.value('data(@id)', 'int') as item_id,
        Notes.Person.value('text()[1]', 'varchar(max)') as notes
from    @xml.nodes('/root/person/item') as Item(Person)
        inner join @xml.nodes('/root/person/notes') as Notes(Person) on
            Notes.Person.value('data(@for)', 'int') = Item.Person.value('data(@id)', 'int')
            and
            Notes.Person.value('data(../@id)', 'int') = Item.Person.value('data(../@id)', 'int')

Обновление!

Я понял это! Я новичок в XQuery, но это работает, поэтому я называю это делом :) Я изменил запрос для заметок на:

Item.Person.value('
    let $id := data(@id)
    return data(../notes[@for=$id])[1]
', 'varchar(max)') as notes

1 Ответ

1 голос
/ 02 декабря 2011

Я бы посоветовал вам сделать cross apply вместо ../, чтобы найти родительский узел.В соответствии с планом запроса это намного быстрее.

select  P.X.value('data(@id)', 'int') as person_id,
        I.X.value('data(@id)', 'int') as item_id,
        I.X.value('let $id := data(@id)
                   return data(../notes[@for=$id])[1]', 'varchar(max)') as notes
from @xml.nodes('/root/person') as P(X)
  cross apply P.X.nodes('item') as I(X)

Вы можете даже удалить ../ во второй строке с одним дополнительным кросс-применением, получая немного больше.

select P.X.value('@id', 'int') as person_id,
       TI.id as item_id,
       P.X.value('(notes[@for = sql:column("TI.id")])[1]', 'varchar(max)') as notes
from @xml.nodes('/root/person') as P(X)
  cross apply P.X.nodes('item') as I(X)
  cross apply (select I.X.value('@id', 'int')) as TI(id) 

Сравнение запросов друг с другом, я получил 67%Ваш запрос 17% на моем первом и 16% на втором.Примечание: эти цифры дают только подсказку о том, какой запрос в действительности будет быстрее.Проверьте свои данные, чтобы знать наверняка.

...