使用xquery从xml中提取数据的最佳方法

发布于 2024-11-08 19:16:24 字数 1651 浏览 0 评论 0原文

考虑以下 xml:

<Persons num="3">
  <Person age="5" />
  <Person age="19" />
</Persons>

需要将此 xml 提取到关系表中:

Persons table (Age1 int, Age2 int, Age3 int , Age4 int)

解析必须满足以下约束:

  • 所有年龄 >=18 的人必须分配到列号最小的列,并且该值必须为 18
  • 如果未给出该人的年龄,则等于 18
  • 所有年龄 <18 岁的人都必须遵循
  • 如果少于 4 人,则未提供的人必须年龄=-1

在给定的示例中,有 3人,提供其中 2 人的年龄:分别为 5 岁和 19 岁。 Persons 表的内容必须如下所示:

18 18 5 -1

是否有使用 xpath 执行此操作的最佳方法?

到目前为止,我可以解析 xml 并分配年龄,但不清楚的是如何进行排序:

declare @XmlData xml = 
'<Persons num="3">
    <Person age="5" />
    <Person age="19" />
</Persons>'

declare @Persons table (Age1 int, Age2 int, Age3 int , Age4 int)
insert into @Persons (Age1, Age2, Age3, Age4)
select ISNULL(Age1, case when Num>= 1 then 18 else -1 end) Age1
    , ISNULL(Age2, case when Num>= 2 then 18 else -1 end) Age2
    , ISNULL(Age3, case when Num>= 3 then 18 else -1 end) Age3
    , ISNULL(Age4, case when Num>= 4 then 18 else -1 end) Age4
from (
    select Persons.Person.value('@num','smallint') as Num
          ,Persons.Person.value('Person[@age<18][1]/@age','smallint') as Age1
          ,Persons.Person.value('Person[@age<18][2]/@age','smallint') as Age2
          ,Persons.Person.value('Person[@age<18][3]/@age','smallint') as Age3
          ,Persons.Person.value('Person[@age<18][4]/@age','smallint') as Age4
    from @XmlData.nodes('/Persons') Persons(Person)
 ) Persons  

select *
from @Persons

结果是

5 18 18 -1

Consider the following xml:

<Persons num="3">
  <Person age="5" />
  <Person age="19" />
</Persons>

There is a need to extract this xml into a relational table:

Persons table (Age1 int, Age2 int, Age3 int , Age4 int)

Parsing has to satisfy the following constraints:

  • all persons with age >=18 must be assigned to columns with smallest column number and the value has to be 18
  • if the age of the person is not given it is equal to 18
  • all persons with age <18 must follow
  • if there are less than 4 persons, those which are not provided must have age=-1

In a given example, there are 3 persons, ages of 2 of them are provided: 5 and 19 respectively. The content of the table Persons has to be the following:

18 18 5 -1

Is there the best way to do so with xpath?

Till now I can parse the xml and assign ages but what is not clear is to how make ordering:

declare @XmlData xml = 
'<Persons num="3">
    <Person age="5" />
    <Person age="19" />
</Persons>'

declare @Persons table (Age1 int, Age2 int, Age3 int , Age4 int)
insert into @Persons (Age1, Age2, Age3, Age4)
select ISNULL(Age1, case when Num>= 1 then 18 else -1 end) Age1
    , ISNULL(Age2, case when Num>= 2 then 18 else -1 end) Age2
    , ISNULL(Age3, case when Num>= 3 then 18 else -1 end) Age3
    , ISNULL(Age4, case when Num>= 4 then 18 else -1 end) Age4
from (
    select Persons.Person.value('@num','smallint') as Num
          ,Persons.Person.value('Person[@age<18][1]/@age','smallint') as Age1
          ,Persons.Person.value('Person[@age<18][2]/@age','smallint') as Age2
          ,Persons.Person.value('Person[@age<18][3]/@age','smallint') as Age3
          ,Persons.Person.value('Person[@age<18][4]/@age','smallint') as Age4
    from @XmlData.nodes('/Persons') Persons(Person)
 ) Persons  

select *
from @Persons

Result is

5 18 18 -1

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

浪菊怪哟 2024-11-15 19:16:25

另一种解决方案需要更多的 sql 代码,但预计执行计划的成本仅为 80 左右。

问题陈述有一个约束:Persons/@num必须等于许多Person标签

限制是:

  • 每个房间的人数有限

这是sql代码:

--//initial xml data
declare @XmlData xml = 
'<Persons roomid="1" num="3">
    <Person age="19" />
    <Person age="10" /> 
    <Person age="5" />
</Persons>
<Persons roomid="4" num="4">
    <Person age="17" />
    <Person age="10" /> 
    <Person age="5" />
    <Person age="1" />
</Persons>'

--//shade xml into temporal table: rank is applied to an age in descreasing order
declare @tmp table (age int, roomid int, orderid int)
insert into @tmp(age,roomid,orderid)
select Persons.age
      ,Persons.roomid
      ,ROW_NUMBER () over (partition by Persons.roomid order by Persons.age desc)
from(
    select Ps.P.value('(@age)[1]','smallint') age       
          ,Ps.P.value('(../@roomid)[1]','smallint') roomid
    from @XmlData.nodes('/Persons/Person') Ps(P)
)Persons
order by Persons.roomid,Persons.age desc    

--//provide ordering for roomid: since roomid may be different (the only thing that is required that roomid is unique)
declare @roomidmapping table (roomid int, roomorderid int)
insert into @roomidmapping(roomid, roomorderid)
select roomid, ROW_NUMBER () over  (order by roomid asc)
from @tmp
group by roomid
declare @roomnumber int = @@ROWCOUNT
--//final result
;WITH ConsequtiveNums AS
(
    SELECT 1 AS Number
    UNION ALL
    SELECT Number+1
    FROM ConsequtiveNums
    WHERE Number<@roomnumber
)
select (select case when age>18 then 18 else age end from @tmp T inner join @roomidmapping M on T.roomid = M.roomid where T.orderid = 1 and M.roomorderid = CN.Number)
      ,(select case when age>18 then 18 else age end from @tmp T inner join @roomidmapping M on T.roomid = M.roomid where T.orderid = 2 and M.roomorderid = CN.Number)
      ,(select case when age>18 then 18 else age end from @tmp T inner join @roomidmapping M on T.roomid = M.roomid where T.orderid = 3 and M.roomorderid = CN.Number)
      ,(select case when age>18 then 18 else age end from @tmp T inner join @roomidmapping M on T.roomid = M.roomid where T.orderid = 4 and M.roomorderid = CN.Number)
from ConsequtiveNums CN

Another solution requires a bit more sql code but costs only ~80 in estimated execution plan.

There is one constraint wrt the problem statement: Persons/@num has to be equal to a number of Person tags

Limitations are:

  • limited number of persons per room

Here is sql code:

--//initial xml data
declare @XmlData xml = 
'<Persons roomid="1" num="3">
    <Person age="19" />
    <Person age="10" /> 
    <Person age="5" />
</Persons>
<Persons roomid="4" num="4">
    <Person age="17" />
    <Person age="10" /> 
    <Person age="5" />
    <Person age="1" />
</Persons>'

--//shade xml into temporal table: rank is applied to an age in descreasing order
declare @tmp table (age int, roomid int, orderid int)
insert into @tmp(age,roomid,orderid)
select Persons.age
      ,Persons.roomid
      ,ROW_NUMBER () over (partition by Persons.roomid order by Persons.age desc)
from(
    select Ps.P.value('(@age)[1]','smallint') age       
          ,Ps.P.value('(../@roomid)[1]','smallint') roomid
    from @XmlData.nodes('/Persons/Person') Ps(P)
)Persons
order by Persons.roomid,Persons.age desc    

--//provide ordering for roomid: since roomid may be different (the only thing that is required that roomid is unique)
declare @roomidmapping table (roomid int, roomorderid int)
insert into @roomidmapping(roomid, roomorderid)
select roomid, ROW_NUMBER () over  (order by roomid asc)
from @tmp
group by roomid
declare @roomnumber int = @@ROWCOUNT
--//final result
;WITH ConsequtiveNums AS
(
    SELECT 1 AS Number
    UNION ALL
    SELECT Number+1
    FROM ConsequtiveNums
    WHERE Number<@roomnumber
)
select (select case when age>18 then 18 else age end from @tmp T inner join @roomidmapping M on T.roomid = M.roomid where T.orderid = 1 and M.roomorderid = CN.Number)
      ,(select case when age>18 then 18 else age end from @tmp T inner join @roomidmapping M on T.roomid = M.roomid where T.orderid = 2 and M.roomorderid = CN.Number)
      ,(select case when age>18 then 18 else age end from @tmp T inner join @roomidmapping M on T.roomid = M.roomid where T.orderid = 3 and M.roomorderid = CN.Number)
      ,(select case when age>18 then 18 else age end from @tmp T inner join @roomidmapping M on T.roomid = M.roomid where T.orderid = 4 and M.roomorderid = CN.Number)
from ConsequtiveNums CN
蛮可爱 2024-11-15 19:16:24

我发现了一个有点肮脏的解决方案:

select ISNULL(Age1, case when Num>= 1 then 18 else -1 end) Age1
    , ISNULL(Age2, case when Num>= 2 then 18 else -1 end) Age2
    , ISNULL(Age3, case when Num>= 3 then 18 else -1 end) Age3
    , ISNULL(Age4, case when Num>= 4 then 18 else -1 end) Age4
from (
    select Persons.Person.value('@num','smallint') as Num
          ,Persons.Person.value('xs:integer(fn:number(@num))+1','int') as Num1
          ,Persons.Person.value('Person[@age<18][xs:integer(fn:number(../@num))][1]/@age','smallint') as Age1
          ,Persons.Person.value('Person[@age<18][xs:integer(fn:number(../@num))-1][1]/@age','smallint') as Age2
          ,Persons.Person.value('Person[@age<18][xs:integer(fn:number(../@num))-2][1]/@age','smallint') as Age3
          ,Persons.Person.value('Person[@age<18][xs:integer(fn:number(../@num))-3][1]/@age','smallint') as Age4 
    from @XmlData.nodes('/Persons') Persons(Person)
 ) Persons

解决方案的想法是首先提取那些>=18的联系人,然后提取那些0<=18的联系人。年龄< 18 最后将未提供的设置为 -1

UPD:尽管解决方案提供了正确的结果,但其成本很高:估计执行计划中约为 1000

I have found a bit dirty solution:

select ISNULL(Age1, case when Num>= 1 then 18 else -1 end) Age1
    , ISNULL(Age2, case when Num>= 2 then 18 else -1 end) Age2
    , ISNULL(Age3, case when Num>= 3 then 18 else -1 end) Age3
    , ISNULL(Age4, case when Num>= 4 then 18 else -1 end) Age4
from (
    select Persons.Person.value('@num','smallint') as Num
          ,Persons.Person.value('xs:integer(fn:number(@num))+1','int') as Num1
          ,Persons.Person.value('Person[@age<18][xs:integer(fn:number(../@num))][1]/@age','smallint') as Age1
          ,Persons.Person.value('Person[@age<18][xs:integer(fn:number(../@num))-1][1]/@age','smallint') as Age2
          ,Persons.Person.value('Person[@age<18][xs:integer(fn:number(../@num))-2][1]/@age','smallint') as Age3
          ,Persons.Person.value('Person[@age<18][xs:integer(fn:number(../@num))-3][1]/@age','smallint') as Age4 
    from @XmlData.nodes('/Persons') Persons(Person)
 ) Persons

The idea of a solution is to first extract those contacts that are >=18, then extract those that are 0 < age < 18 and finally set those that are not provided to -1

UPD: despite the fact that solution provided correct results, its cost is high: ~1000 in estimated execution plan

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文