I've got a big XML file containing data concerning a Hotel chain. Per week, per day, per hotel they keep some data for their report:
<Report week="1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="report.xsd">
<Days>
<Day id="1" naam="Monday">
<Hotels>
<Hotel id="1" naam="Bonotel Antwerp">
<Stays>
<Stay id="1">
<Room id="1" number="1"/>
<Roomtype id="1" naam="oneperson" price="20.00"/>
<Period id="1" naam="lowseason" price="20.00"/>
<Formula id="1" naam="roomandbreakfast" price="20.00"/>
<Facilities>
<Facility id="1" naam="swimming" price="5.00"/>
<Facility id="2" naam="golf" price="20.00"/>
</Facilities>
<Guest id="1" naam="John Williams"/>
</Stay>
<Stay id="2">
<Room id="2" number="2"/>
<Roomtype id="1" naam="oneperson" price="20.00"/>
<Period id="1" naam="lowseason" price="20.00"/>
<Formula id="1" naam="roomandbreakfast" price="20.00"/>
<Facilities>
<Facility id="2" naam="golf" price="20.00"/>
<Facility id="3" naam="minibar" price="10.00"/>
</Facilities>
<Guest id="2" naam="Ray Kurzweil"/>
</Stay>
<Stay id="3">
<Room id="3" number="3"/>
<Roomtype id="2" naam="twoperson" price="40.00"/>
<Period id="1" naam="lowseason" price="20.00"/>
<Formula id="2" naam="halfpension" price="30.00"/>
<Facilities>
<Facility id="4" naam="tennis" price="20.00"/>
<Facility id="4" naam="tennis" price="20.00"/>
</Facilities>
<Guest id="3" naam="Stephen Hawking"/>
</Stay>
</Stays>
</Hotel>
(: ... Other Hotels ... :)
</Hotels>
</Day>
(: ... Other Days ... :)
</Days>
</Report>
Using XQuery, I have to calculate what the average guests spends. This is the sum of the Roomtype price, the Periode price, the Formula price and the sum of the facilities. I came up with this XQuery:
xquery version "1.0";
<ReportResult week="1">
{
for $x in (1 to 7)
return
for $stays in doc("report.xml")//Report/Days/Day[#id=$x]/Hotels/Hotel[#id=1]/Stays
let $average := avg(
for $v in $stays/Stay
return sum($v/Roomtype/#price) + sum($v/Facilities/Facility/#prijs) + sum($v/Formula/#price) + sum($v/Period/#price)
)
return
<AverageSpending hotel="Bonotel Antwerpen" day="{data($x)}">
{data(round-half-to-even($average, 2))}
</AverageSpending>
}
</Reportresult>
This produces the result I expect:
<Reportresult week="1">
<Averagespending hotel="Bonotel Antwerpen" day="1">101.67</AverageSpending>
<Averagespending hotel="Bonotel Antwerpen" day="2">321.67</AverageSpending>
(: ... etc... :)
<Averagespending hotel="Bonotel Antwerpen" day="2">255</AverageSpending>
</Reportresult>
However, I also would like to calculate the total average. So the sum of all averages divided by the 7 days to output something like
<TotalAverage>198,67</TotalAverage>
But I'm having trouble calculating this Total Average. In Java I would use a total variable and increment it with the calculated average each loop but obviously that doesn't work here. How would I be able to do this with XQuery? Thanks.
By example you can assign to a variable the first part of your request and calculate the total average on it and concat the both results :
xquery version "1.0";
<ReportResult week="1">
{ let $averages :=
for $x in (1 to 7)
return
for $stays in doc("report.xml")//Report/Days/Day[#id=$x]/Hotels/Hotel[#id=1]/Stays
let $average := avg(
for $v in $stays/Stay
return sum($v/Roomtype/#price) + sum($v/Facilities/Facility/#prijs) + sum($v/Formula/#price) + sum($v/Period/#price)
)
return
<AverageSpending hotel="Bonotel Antwerpen" day="{data($x)}">
{data(round-half-to-even($average, 2))}
</AverageSpending>
return ($averages,<TotalAverage>{avg($averages)}</TotalAverage>)
</Reportresult>
Related
The example xml looks like this:
<sales>
............
<customer custid="108">
<name>NORTH WOODS HEALTH AND FITNESS SUPPLY CENTER</name>
<address>98 LONE PINE WAY</address>
<city>HIBBING</city>
<state>MN</state>
<zip>55649</zip>
<area>612</area>
<phone>566-9123</phone>
<repid>7844</repid>
<creditlimit>8000</creditlimit>
<ord ordid="613">
<orderdate>1987-02-01</orderdate>
<shipdate>1987-02-01</shipdate>
<total>6400</total>
<item itemid="1">
<product_ref ref="100871"/>
<actualprice>5.6</actualprice>
<qty>100</qty>
<itemtot>560</itemtot>
</item>
</ord>
</customer>
<product prodid="100860">
<descrip>ACE TENNIS RACKET I</descrip>
<price>
<stdprice>35</stdprice>
<minprice>28</minprice>
<startdate>1986-06-01</startdate>
</price>
</product>
.........
</sales>
let $product := doc('sales.xml')/sales/product
for $item in doc('sales.xml')/sales/customer/ord/item
where $item/actualprice < $product[#prodid=$item/product_ref/#ref]/price/minprice
return $item
And I get as result this:
<item itemid="1">
<product_ref ref="100861"/>
<actualprice>35</actualprice>
<qty>1</qty>
<itemtot>35</itemtot>
</item>
<item itemid="3">
<product_ref ref="101863"/>
<actualprice>10</actualprice>
<qty>150</qty>
<itemtot>1500</itemtot>
</item>
<item itemid="7">
<product_ref ref="101863"/>
<actualprice>12.5</actualprice>
<qty>200</qty>
<itemtot>2500</itemtot>
</item>
<item itemid="5">
<product_ref ref="101863"/>
<actualprice>9</actualprice>
<qty>100</qty>
<itemtot>900</itemtot>
</item>
But the items with ref=101863 are not right. Only the item with id=5 is less then minprice.
Why does this error occur? Tried a lot of different queries but it gives me always the same result.
It works fine with ref=100861.
Assuming the query isn't schema-aware, you are comparing the two prices as strings, not as numbers. You need to convert both to numbers before comparison.
First of all, I'm totally new in Xquery and I am sorry my problem is very stupid :/ .
This is just a part of my XML :
<books>
<book id="b1">
<title>Set theory and the continuum problem</title>
<category>Mathematics</category>
<location>
<area>hall1</area>
<case>1</case>
<shelf>2</shelf>
</location>
<description>A lucid, elegant, and complete survey of set theory.</description>
<history>
<borrowed by="m4"/>
<borrowed by="m2" until="2018-04-05"/>
</history>
</book>
<book id="b2">
<title>Computational Complexity</title>
<isbn>978-0201-530-827</isbn>
<category>Computer Science</category>
<location>
<area>hall1</area>
<case>3</case>
<shelf>3</shelf>
</location>
<description>.</description>
</book>
<book id="b3">
<title>To mock a mockingbird</title>
<isbn>1-292-09761-2</isbn>
<category>Logic</category>
<category>Mathematics</category>
<location>
<area>hall1</area>
<case>1</case>
<shelf>3</shelf>
</location>
<description>.</description>
</book>
</books>
<libraryArea floor="1" name="hall1">
<bookcases>
<woodenCase id="3" shelves="5"/>
<woodenCase id="2" shelves="5"/>
<steelCase id="1" shelves="5"/>
</bookcases>
</libraryArea>
<libraryArea name="hall2">
<bookcases>
<lockedCase id="1" shelves="9"/>
<steelCase id="4" shelves="3"/>
<lockedCase id="3" shelves="2"/>
<steelCase id="2" shelves="4"/>
</bookcases>
</libraryArea>
<libraryArea name="hall3">
<bookcases>
<woodenCase id="2" shelves="3"/>
<steelCase id="1" shelves="8"/>
</bookcases>
</libraryArea>
<libraryArea name="archive">
<bookcases>
<lockedCase id="1" shelves="1"/>
</bookcases>
</libraryArea>
</library>
I want to list specific book titles in their own tags ''
Something like this:
<specialSteelCases>
<area name="hall1">
<steelCase id="1">
<bookCount>2</bookCount>
<book>Set theory and the continuum problem</book>
<book>To mock a mockingbird</book>
<shelves>5</shelves>
</steelCase>
</area>
</specialSteelCases>
But I get this, all book titles in just one book tag:
< book > Set theory and the continuum problemTo mock a mockingbird < / book >
Is there a way I can separate them apart, so each book title have their own
This is my Xquery:
<specialSteelCases>
{
for $a in //libraryArea
where $a/bookcases[count(woodenCase) > 1 ]
order by $a/#id
return
<area name="{$a/#name}">
{
for $s in $a//bookcases/steelCase
let $bbNew := (//books/book[location/case=$s/#id][location/area=$a/#name])
return
<steelCase id="{$s/#id}">
<bookCount>***code***</bookCount>
<book>{$bbNew/title/text()}</book>
<shelves>***code**</shelves>
</steelCase>
}
</area>
}
</specialSteelCases>
Use a let clause to select the relevant books and then count them and additionally with a nested for expression output the titles:
<specialSteelCases>
{
for $a in //libraryArea
where $a/bookcases[count(woodenCase) > 1 ]
order by $a/#id
return
<area name="{$a/#name}">
{
let $books := //books/book[location/case=$a//bookcases/steelCase/#id][location/area=$a/#name]
return
<steelCase id="{$a//bookcases/steelCase/#id}">
<bookCount>{count($books)}</bookCount>
{
for $title in $books/title
return
<book>{data($title)}</book>
}
</steelCase>
}
</area>
}
</specialSteelCases>
https://xqueryfiddle.liberty-development.net/jyyiVhw/1
If you can move to XQuery 3 and simply grouping:
for $book in //books/book
group by $case := //library/libraryArea[#name = $book/location/area]/bookcases/*[#id = $book/location/case]/local-name()
return
element {$case } {
<bookCount>{count($book)}</bookCount>
,
$book/title ! <book>{data()}</book>
}
https://xqueryfiddle.liberty-development.net/jyyiVhw
For the XML data below I am trying to get the output shown here: I.e I want to see the names of the countries having a population greater than 20000 , with the conditions that the number of cities displayed should only be for those with a population more than 3500. Also, For some countries the city is within a province.
<result>
<country name="B">
<num_cities>3</num_cities>
</country>
<country name="C">
<num_cities>2</num_cities>
</country>
</result>
---------------------------This is the XML data----------------------
<country id="1" name="A" population="12000">
<name>A</name>
<city id="c1" country="1">
<name>T1</name>
<population>5000</population>
</city>
<city id="c2" country="1">
<name>T2</name>
<population>3000</population>
</city>
<city id="c3" country="1">
<name>T3</name>
<population>4000</population>
</city>
</country>
<country id="3" name="B" population="80000">
<name>B</name>
<city id="c4" country="2">
<name>T4</name>
<population>6000</population>
</city>
<city id="c5" country="2">
<name>T5</name>
<population>2000</population>
</city>
<city id="c6" country="2">
<name>T6</name>
<population>60000</population>
</city>
<city id="c7" country="2">
<name>T7</name>
<population>12000</population>
</city>
</country>
<country id="3" name="C" population="68000">
<name>C</name>
<province>P1</province>
<city id="c8" country="3">
<name>T8</name>
<population>51000</population>
</city>
<city id="c9" country="3">
<name>T9</name>
<population>3000</population>
</city>
<city id="c10" country="3">
<name>T10</name>
<population>14000</population>
</city>
</country>
I wrote this xquery but i don't know how to exclude the cities having a population > 3500. I might not have written to code correctly either...Please assist.
for $c in doc("abc")//country
let $city:= count($c/city/name)
let $citypr:= count($c/province/city/name)
where $c/#population>1000000
return
<result>
<country name='{data($c/name) }'></country>
<num_of_cities>
{
if (exists ($c/city/name)) then
$city
else
$citypr
}
</num_of_cities>
</result>
Some hints:
Don't bother with where clauses where you can filter down earlier, in a specifier retrieving your content.
If you want to have only one <result>, rather than one per datum, you need to start it before the FLWOR expression; whatever you 'return' will be returned once per item.
This is an example of something closer:
<result>{
for $large-country in doc("abc")//country[#population > 20000]
let $large-cities := $country/city[population > 3500]
return
<country name="{$large-country/#name}">
<num_cities>{count($large-cities)}</num_cities>
</country>
}</result>
I am new to Xquery, I want to change the given xml into another xml format.
Given XML:
<?xml version="1.0" encoding="UTF-8"?>
<Store>
<consumer id="H01">
<name>John Doe</name>
<items>
<item type = "Torch">
<price>$3</price>
</item>
<item type = "Gas">
<price>$4</price>
</item>
</items>
</consumer >
<consumer id="H05">
<name>Jane Doe</name>
<items>
<item type = "Cell">
<price>$8</price>
</item>
<item type = "Shirt">
<price>$12</price>
</item>
</items>
</consumer>
Desired XML Format:
<Store>
<user>
<number><id>H01</id><name>John Doe</name></number>
<number><id>H05</id><name>Jane Doe</name></number>
</user>
<inventory>
<number><type>Torch</type><price>$3</price></number>
<number><type>Gas</type><price>$4</price></number>
<number><type>Cell</type><price>$8</price></number>
<number><type>Shirt</type><price>$12</price></number>
</inventory>
</Store>
Xquery I made:
for $customer in distinct-values(doc("../xml/store.xml")/store/consumer/#id)
let $name := doc("../xml/store.xml")/store/consumer[#id=$customer]/name
for $object in distinct- values(doc("../xml/store.xml")/store/consumer[#id=$customer]/items/item/#type)
return
<store>
<user>
<number>
<id>{$customer}</id>
{$name}
</number>
</user>
<inventory>
<number>
<type>{$object}</type>
</number>
</inventory>
</store>
Where exactly am I going wrong? Is there a way we could make attributes as new node elements.
your two for clauses are nested, you don't want that in this case. I would do something like this:
let $doc := doc("../xml/store.xml")
let $customerIds := distinct-values($doc/store/consumer/#id)
return
<store>
<user>{for $customerId in $customerIds
let $consumer := $doc/store/consumer[#id=$customerId]
return <number><id>{data($consumer/#id)}</id><name>{$consumer/name}</name></number>
}
</user>
<inventory>
similar thing for items
</inventory>
</store>
Is there a way we could make attributes as new node elements.
Yes you can for example use the data() function to extract the text. See example above.
I have a SQL Server database, and I need to populate it with returned xml from an api call.
This is the xml code that's returned(not in a file):
<petfinder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://api.petfinder.com/schemas/0.9/petfinder.xsd">
<header>
<version>0.1</version>
<timestamp>2013-04-08T14:52:23Z</timestamp>
<status>
<code>100</code>
<message/>
</status>
</header>
<lastOffset>25</lastOffset>
<pets>
<pet>
<id>18589607</id>
<shelterId>OK98</shelterId>
<shelterPetId>11C-0015</shelterPetId>
<name>Sam</name>
<animal>Cat</animal>
<breeds>
<breed>Domestic Short Hair</breed>
<breed>Tabby</breed>
</breeds>
<mix>yes</mix>
<age>Adult</age>
<sex>M</sex>
<size>XL</size>
<options>
<option>altered</option>
<option>hasShots</option>
<option>housebroken</option>
</options>
<description>
<![CDATA[
<div>This guy loves the camera. Look at him pose and show off! Sam is about 5 years old and is a cream Tabby. He is good with other cats and is house trained. He has turquoise eyes and is a sweet sweet cat. Sam loves to be the right hand man and assist you on any task you may have. Sammy is not the type of cat that likes to be held but will sit right next to you for some rubbing and head butting. Our adoption fee is $100 for dogs and $75 for cats. This adoption fee includes the spay or neutering and rabies shot. </div>
]]>
</description>
<lastUpdate>2012-07-24T14:50:17Z</lastUpdate>
<status>A</status>
<media>
<photos>
<photo id="1" size="x">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-1-x.jpg
</photo>
<photo id="1" size="fpm">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-1-fpm.jpg
</photo>
<photo id="1" size="pn">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-1-pn.jpg
</photo>
<photo id="1" size="pnt">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-1-pnt.jpg
</photo>
<photo id="1" size="t">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-1-t.jpg
</photo>
<photo id="2" size="x">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-2-x.jpg
</photo>
<photo id="2" size="fpm">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-2-fpm.jpg
</photo>
<photo id="2" size="pn">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-2-pn.jpg
</photo>
<photo id="2" size="pnt">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-2-pnt.jpg
</photo>
<photo id="2" size="t">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-2-t.jpg
</photo>
<photo id="3" size="x">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-3-x.jpg
</photo>
<photo id="3" size="fpm">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-3-fpm.jpg
</photo>
<photo id="3" size="pn">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-3-pn.jpg
</photo>
<photo id="3" size="pnt">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-3-pnt.jpg
</photo>
<photo id="3" size="t">
http://photos.petfinder.com/photos/US/OK/OK98/18589607/OK98.18589607-3-t.jpg
</photo>
</photos>
</media>
<contact>
<address1>714 Martin Luther King Jr Ave</address1>
<address2/>
<city>Duncan</city>
<state>OK</state>
<zip>73533</zip>
<phone/>
<fax/>
<email/>
</contact>
</pet>
...
More specifically, I need to take the nodes for ID, name, animal, description, and several others, and insert them into their respectful columns in my database.
And it must repeat this for each "pet" node that these are all in.
Can I do this in VB.net without saving a file, just as an xml string?
Please help, I've been stuck on this for days.
Assuming you have your XML structure in a variable (or stored procedure parameter) of type XML, you can do something like this:
CREATE PROCEDURE dbo.InsertXmlData
#XmlData XML
AS BEGIN
INSERT INTO dbo.YourTable(ID, PetName, Animal, Description)
SELECT
ID = Pet.value('(id)[1]', 'int'),
PetName = Pet.value('(name)[1]', 'varchar(50)'),
Animal = Pet.value('(animal)[1]', 'varchar(50)'),
[Description] = Pet.value('(description)[1]', 'varchar(500)')
FROM
#XmlData.nodes('/petfinder/pets/pet') AS xTBL(Pet)
END
That gives you the info in those nodes as a set of rows and columns which you can easily insert into a SQL Server table. So now you just need to find a way to call this stored procedure from your VB.NET code and pass in the XML into the #XmlData parameter
Here's an example of how you can extract the data for each pet from the XML using XPath and the XmlDocument class:
Dim doc As XmlDocument = New XmlDocument()
doc.LoadXml(xmlString)
For Each pet As XmlNode In doc.SelectNodes("/petfinder/pets/pet")
Dim id As String = pet.SelectSingleNode("id").InnerText
Dim name As String = pet.SelectSingleNode("name").InnerText
' ...
Next
I'm assuming you know how to save the data to your SQL database from there.