【问题标题】:How to write an xquery containing sequence elements?如何编写包含序列元素的 xquery?
【发布时间】:2009-06-12 00:23:10
【问题描述】:

我有一个非常大的 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>

数据的结构不在我的控制之下。此数据当前位于 MS SQL Server 2005 中单行中的单个 xml 列中。我正在尝试构建一个查询,该查询会产生以下输出:

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 leavetime 序列元素以获取我在上面指出的所需行集。

谢谢。

更新: 我想补充一点,给定的人员记录可能根本没有 no entertime/leavetime 序列元素,但仍需要在行集中返回。我已经更新了所需输出的示例以反映这一点。

【问题讨论】:

    标签: sql-server xml tsql xquery


    【解决方案1】:
    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
    

    【讨论】:

    • 我注意到,似乎通过将节点函数的根元素更改为序列,然后加强层次结构以获得其他值,查询的性能会受到极大的影响。我试图通过更新我的查询以使用这种格式来验证返回的记录数,但是在它运行 30 分钟但没有结果后我取消了查询。我正在使用的数据集中有相当于 215,000 条个人记录。
    • 我应该补充一点,虽然这个答案并没有让我 100% 达到我想要的目标,但它对我展示如何加入 xml 数据非常有帮助。
    • 很高兴有帮助。可以很容易地消除到父元素的步骤,只需使用三个连接的表(一个用于名称/id,一个用于输入,一个用于叶子)。
    • 我一直在做一些测试,并且遍历到父级在幕后造成了一些非常可怕的事情。随着返回更多结果,以下查询本身似乎变得越来越慢,并最终在第 3,000 行(大约 215,000 行)附近阻塞: SELECT [MemberNumber] = t.c.value('(../PersonId)[1] ','NVARCHAR(20)') ,[Entertime] = t.c.value('.', 'DATETIME') FROM @x.nodes('root/person/entertime') t(c) 非常有趣。我不确定发生了什么。
    • 您是否尝试使用 3 个表并加入,而不是 2 个?一次性提取 ID/FirstName/LastName,然后 @x.nodes('root/person') 加入 @x.nodes(/root/person/entertime) 和 @x.nodes(/root/person/leavetime)。这应该更快,因为每个“表”只向前扫描 XML。
    【解决方案2】:

    我接受了 Remus 的回答,因为它让我 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
    

    【讨论】:

      【解决方案3】:

      尚未意识到文档中可能有多个人。在这种情况下,我的查询无论如何都是不正确的。我想如果你先把每个人分解成自己的 XML 片段,然后提取十个进入/离开时间可能会更好。我没有 215k 人 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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-02-19
        • 2015-09-04
        • 1970-01-01
        • 2021-04-26
        • 1970-01-01
        相关资源
        最近更新 更多