Как написать xquery, содержащий элементы последовательности? - PullRequest
0 голосов
/ 12 июня 2009

У меня есть очень большой набор данных xml, который структурирован следующим образом:

<root>
    <person>
        <personid>HH3269732</personid>
        <firstname>John</firstname>
        <lastname>Smith</lastname>
        <entertime>01/02/2008 10:15</entertime>
        <leavetime>01/02/2008 11:45</leavetime>
        <entertime>03/01/2008 08:00</entertime>
        <leavetime>03/01/2008 10:00</leavetime>
        ... 
        // number of enter times and leave times vary from person to person
        // there may not be a final leave time (ie, they haven't left yet)
    </person>
    ...
</root>

Структура данных не находится под моим контролем. Эти данные в настоящее время находятся в одном столбце xml в одной строке в MS SQL Server 2005. Я пытаюсь создать запрос, который приводит к следующему выводу:

HH3269732   John   Smith   01/02/2008 10:15   01/02/2008 11:45
HH3269732   John   Smith   03/01/2008 08:00   01/02/2008 10:00
HH3269735   Mark   Pines   02/01/2008 09:00   NULL
HH3263562   James  Frank   NULL               NULL
HH3264237   Harold White   04/18/2008 03:00   04/18/2008 05:00
...

Мой запрос в настоящее время выглядит следующим образом:

DECLARE @xml xml
SELECT @xml = XmlCol FROM Data

SELECT
    [PersonId] = Persons.PersonCollection.value('(personid)[1]', 'NVARCHAR(50)')
    ,[First Name] = Persons.PersonCollection.value('(firstname)[1]', 'NVARCHAR(50)')
    ,[Last Name] = Persons.PersonCollection.value('(lastname)[1]', 'NVARCHAR(50)')
    ??????
FROM @xml.nodes('root\person') Persons(PersonCollection)

Этот запрос может быть не на 100% правильным, так как я вытаскиваю его из памяти, но проблема, с которой я столкнулся, заключается в том, что я не знаю, как включить элементы последовательности времени выхода entertime таким образом, чтобы получить желаемый набор строк, который я указал выше.

Спасибо.

UPDATE: Я хотел добавить, что данная запись о человеке может вообще иметь элементы последовательности no entertime / lefttime, но все же должна быть возвращена в наборе строк. Я обновил пример желаемого результата, чтобы отразить это.

Ответы [ 3 ]

1 голос
/ 12 июня 2009
with cte_entertime as (
SELECT
    [PersonId] = t.c.value('(../personid)[1]', 'NVARCHAR(50)')
    ,[First Name] = t.c.value('(../firstname)[1]', 'NVARCHAR(50)')
    ,[Last Name] = t.c.value('(../lastname)[1]', 'NVARCHAR(50)')
    ,[Entertime] = t.c.value('.', 'NVARCHAR(50)')
    ,[entry_number] = ROW_NUMBER() OVER (ORDER BY t.c)
FROM @x.nodes('root/person/entertime') t(c))
, cte_leavetime as (
    SELECT
    [Leavetime] = t.c.value('.', 'NVARCHAR(50)')
    ,[entry_number] = ROW_NUMBER() OVER (ORDER BY t.c)
FROM @x.nodes('root/person/leavetime') t(c))
SELECT PersonID
    , [First Name]
    , [Last Name] 
    , [Entertime]
    , [Leavetime]
    FROM cte_entertime e 
    LEFT OUTER JOIN cte_leavetime l on e.entry_number = l.entry_number
0 голосов
/ 12 июня 2009

Не понял, что в документе может быть несколько человек. В любом случае мой запрос будет неверным. Я подумал, может быть, если вы сначала разделите каждого человека на отдельный фрагмент XML, а десять извлечут время ввода / вывода, может быть лучше. У меня нет 215 тыс. Человек XML, чтобы попробовать, но вот идея:

declare @x xml;
select @x = N'<root>
    <person>
        <personid>HH3269732</personid>
        <firstname>John</firstname>
        <lastname>Smith</lastname>
        <entertime>01/02/2008 10:15</entertime>
        <leavetime>01/02/2008 11:45</leavetime>
        <entertime>03/01/2008 08:00</entertime>
        <leavetime>03/01/2008 10:00</leavetime>
        <entertime>04/01/2008 08:00</entertime>
    </person>
    <person>
        <personid>HH3269733</personid>
        <firstname>Jane</firstname>
        <lastname>Doe</lastname>
        <entertime>01/03/2008 10:15</entertime>
        <leavetime>01/03/2008 11:45</leavetime>
        <entertime>03/04/2008 08:00</entertime>
        <leavetime>03/04/2008 10:00</leavetime>
        <entertime>04/04/2008 08:00</entertime>
    </person>
</root>';


with cte_person as (
    select
        t.c.value('(personid)[1]', 'NVARCHAR(50)') as personid
        , t.c.value('(firstname)[1]', 'NVARCHAR(50)') as firstname
        , t.c.value('(lastname)[1]', 'NVARCHAR(50)') as lastname
        , t.c.query('entertime') as entertime
        , t.c.query('leavetime') as leavetime
    FROM @x.nodes('root/person') t(c))
, cte_cross_enter as (
    select
        p.personid
        , p.firstname
        , p.lastname
        , x.c.value('.', 'datetime') as entertime
        , row_number() over (partition by personid order by x.c) as row_enter
        from cte_person p
        cross apply p.entertime.nodes('/entertime') x(c))
, cte_cross_leave as (
    select
        p.personid 
        , x.c.value('.', 'datetime') as leavetime
        , row_number() over (partition by personid order by x.c) as row_leave
        from cte_person p
        cross apply p.leavetime.nodes('/leavetime') x(c))
select e.personid
    , e.firstname
    , e.lastname
    , e.entertime
    , l.leavetime
    from cte_cross_enter e
    left outer join cte_cross_leave l 
            on e.personid = l.personid and 
            e.row_enter = l.row_leave
0 голосов
/ 12 июня 2009

Я принял ответ Ремуса, так как он дал мне 95% к решению. Для ознакомления, вот окончательная структура запроса:

with cte_maindata as (
SELECT
    [PersonId] = t.c.value('(personid)[1]', 'NVARCHAR(50)')
    ,[First Name] = t.c.value('(firstname)[1]', 'NVARCHAR(50)')
    ,[Last Name] = t.c.value('(lastname)[1]', 'NVARCHAR(50)')
FROM @x.nodes('root/person') t(c))
, cte_entertime as (
    SELECT
    [PersonId] = t.c.value('(../personid)[1]', 'NVARCHAR(50)')
    ,[Entertime] = t.c.value('.', 'NVARCHAR(50)')
FROM @x.nodes('root/person/entertime') t(c))
, cte_leavetime as (
    SELECT
    [PersonId] = t.c.value('(../personid)[1]', 'NVARCHAR(50)')
    ,[Leavetime] = t.c.value('.', 'NVARCHAR(50)')
FROM @x.nodes('root/person/leavetime') t(c))
SELECT 
    m.PersonID
    ,[First Name]
    ,[Last Name] 
    ,[Entertime]
    ,[Leavetime]
FROM cte_maindata m
    LEFT OUTER JOIN cte_entertime e on m.PersonId = e.PersonId
    LEFT OUTER JOIN cte_leavetime l on m.PersonId = l.PersonId
...