Лучший способ извлечь данные из XML с помощью xquery - PullRequest
3 голосов
/ 20 мая 2011

Рассмотрим следующий 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 -1016 *

В данном примере есть 3 человека, предоставляется возраст 2 из них: 5 и 19 соответственно. Содержание таблицы Персоны должно быть следующим:

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

Ответы [ 2 ]

1 голос
/ 20 мая 2011

Я нашел немного грязное решение:

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, и, наконец, установить те, которые не предоставлены, равными -1 </p>

UPD: несмотря на то, что решение дало правильные результаты, его стоимость высока: ~ 1000 в предполагаемом плане выполнения

0 голосов
/ 21 мая 2011

Другое решение требует немного больше кода 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
...