Find information about newest element in XQuery - xpath

I have a xml file like this:
<carSchema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="carSchema.xsd">
<car>
<License>23</License>
<year>2010</year>
<model>a</model>
<manufacturer>hyundai</manufacturer>
</car>
<car>
<License>24</License>
<year>2002</year>
<model>b</model>
<manufacturer>hyundai</manufacturer>
</car>
<car>
<License>25</License>
<year>2005</year>
<model>c</model>
<manufacturer>hyundai</manufacturer>
</car>
<car>
<License>26</License>
<year>2004</year>
<model>d</model>
</car>
<car>
<License>27</License>
<year>2016</year>
<model>f</model>
<manufacturer>hyundai</manufacturer>
</car>
I want to find information about newest car in Xquery. I wrote this Query that return year of newest car.
xquery version "1.0";
max(
for $x in doc("car.xml")/carSchema/car
order by $x/year descending
return $x/year)
How I return all information about that car(License, model, manufacturer)?

You can use
(for $car in doc("car.xml")/carSchema/car
order by $car/year descending
return $car)[1]/*
to find all child elements of the element with the latest year or
(for $car in doc("car.xml")/carSchema/car
order by $car/year descending
return $car)[1]
to find the element itself with the latest year.

Related

Select Xpath element value based on value of another element

I am trying to capture a value using XPath based on value of a different field.
Example XML:
<?xml version="1.0" encoding="UTF-8" ?>
<employees>
<employee>
<id>1</id>
<firstName>Tom</firstName>
<lastName>Cruise</lastName>
<photo>https://jsonformatter.org/img/tom-cruise.jpg</photo>
</employee>
<employee>
<id>2</id>
<firstName>Maria</firstName>
<lastName>Sharapova</lastName>
<photo>https://jsonformatter.org/img/Maria-Sharapova.jpg</photo>
</employee>
<employee>
<id>3</id>
<firstName>Robert</firstName>
<lastName>Downey Jr.</lastName>
<photo>https://jsonformatter.org/img/Robert-Downey-Jr.jpg</photo>
</employee>
</employees>
I am trying to get Xpath expression for value in the firstName field, when id value is 3.
You can locate parent node based on the known child node and then find the desired child node of that parent, as following:
//employee[./id='3']/firstName
the expression above will give the desired firstName node itself.
To retrieve it's text value this can be used:
//employee[./id='3']/firstName/text()

Xpath queries based on given node

I have a this xml:
<?xml version="1.0"?>
<catalog>
<car>
<id>0</id>
<color>green</color>
<color>red</color>
<color>yellow</color>
<vip>
<user>Trump</user>
<user>Obama</user>
<user>Merkel</user>
</vip>
</car>
<car>
<id>1</id>
<color>green</color>
<color>red</color>
<color>yellow</color>
<vip>
<user>Putinski</user>
<user>Orlovski</user>
<user>Idiotski</user>
</vip>
</car>
<car>
<id>2</id>
<color>green</color>
<color>red</color>
<color>yellow</color>
<vip>
<user>Clooney</user>
<user>Lopez</user>
<user>Ford</user>
</vip>
</car>
</catalog>
And I am fighting with some simple things:
a) count the "color" nodes from car id 0
b) retrieve Obama's car id
For a) I know how to identify car id 0
/catalog/car/id=0
gives me a TRUE - so this is the proof I am on the right track. But now how can I continue counting the "color" nodes based on car id 0? The solution postet here does not work, as well as the following-sibling results in an javax.xml.transformerException. Does anybody know how to solve this?
To count the color nodes in car with id = 0 you can use
count(/catalog/car[id="0"]/color)
Returns 3
To get Obama's car id:
/catalog/car[.//user="Obama"]/id/text()
Returns 0

Efficiently grouping elements that exists in both documents (inner join) in Xquery

I have the following data:
<Subjects>
<Subject>
<Id>1</Id>
<Name>Maths</Name>
</Subject>
<Subject>
<Id>2</Id>
<Name>Science</Name>
</Subject>
<Subject>
<Id>2</Id>
<Name>Advanced Science</Name>
</Subject>
</Subjects>
and:
<Courses>
<Course>
<SubjectId>1</SubjectId>
<Name>Algebra I</Name>
</Course>
<Course>
<SubjectId>1</SubjectId>
<Name>Algebra II</Name>
</Course>
<Course>
<SubjectId>1</SubjectId>
<Name>Percentages</Name>
</Course>
<Course>
<SubjectId>2</SubjectId>
<Name>Physics</Name>
</Course>
<Course>
<SubjectId>2</SubjectId>
<Name>Biology</Name>
</Course>
</Courses>
I wish to efficiently get elements from both documents that share the share the same Ids.
I want to get the result like this:
<Results>
<Result>
<Table1>
<Subject>
<Id>1</Id>
<Name>Maths</Name>
</Subject>
</Table1>
<Table2>
<Course>
<SubjectId>1</SubjectId>
<Name>Algebra I</Name>
</Course>
<Course>
<SubjectId>1</SubjectId>
<Name>Algebra II</Name>
</Course>
<Course>
<SubjectId>1</SubjectId>
<Name>Percentages</Name>
</Course>
</Table2>
</Result>
<Result>
<Table1>
<Subject>
<Id>2</Id>
<Name>Science</Name>
</Subject>
<Subject>
<Id>2</Id>
<Name>Advanced Science</Name>
</Subject>
</Table1>
<Table2>
<Course>
<SubjectId>2</SubjectId>
<Name>Physics</Name>
</Course>
<Course>
<SubjectId>2</SubjectId>
<Name>Biology</Name>
</Course>
</Table2>
</Result>
</Results>
So far I have 2 solutions:
<Results>
{
for $e2 in $t2/Course
let $foriegnId := $e2/SubjectId
group by $foriegnId
let $e1 := $t1/Subject[Id = $foriegnId]
where $e1
return
<Result>
<Table1>
{$e1}
</Table1>
<Table2>
{$e2}
</Table2>
</Result>
}
</Results>
and the otherway round:
<Results>
{
for $e1 in $t1/Subject
let $id := $e1/Id
group by $id
let $e2 := $t2/Course[SubjectId = $id]
where $e2
return
<Result>
<Table1>
{$e1}
</Table1>
<Table2>
{$e2}
</Table2>
</Result>
}
</Results>
Is there a more efficient way of doing this?
Perhaps taking advantages of multiple groups?
Update
A major issue with my code at the moment is that it's performance is highly dependent on which table is bigger. For example the 1st solution is better in cases where the 2nd table is bigger and vice versa.
The solution you have looks reasonable to me. It will perform siginificantly better on a processor like Saxon-EE that does join optimization than on one (like Saxon-HE) that doesn't. If you want to hand-optimize it, your simplest approach is to switch to using XSLT: use the key() function to replace the filter expression $t1/Subject[Id = $foriegnId] which, in the absence of optimization, searches your second file once for each element selected in the first file.

Linq to XML - get elements that have certain child element

Using LINQ to XML, how do I get a collection of all elements that have a named child element.
for example;
<root>
<Garage>
<Car id="001">
<Price PaymentType="Cash">$100</Price>
</Car>
<Car id="002">
<Price PaymentType="Cash">$200</Price>
</Car>
<Car id="003">
</Car>
</Garage>
</root>
this will return 2 Car elements (#1 and #2) as they have the Price element. It won't return Car #3, as it doesn't have a price element.
thanks as always
Assuming you have an XDocument object named doc with your example xml loaded into it. You could try something like this.
IEnumerable<XElement> elements = doc.Descendants("Garage").Elements().Where(e => e.Elements().Any());

Selecting Nodes under two different conditions in XPath

I'm working on a Query in XPath and somehow I just can't get it to work.
I've got more cars in my "garage" of course, but to solve the problem, the two Nodes will do it:
<garage>
<car>
<data>
<brand name="Mazda" model="MX5"></brand>
<country>Japan</country>
<ctype>Cabriolet</ctype>
<motor fueltype="Super">
<ps>146</ps>
<kw>107</kw>
<umin>5000</umin>
</motor>
<price>22000</price>
</data>
</car>
<car>
<data>
<brand name="Audi" model="RS6"></brand>
<country>Germany</country>
<ctype>Limousine</ctype>
<motor fueltype="Super">
<ps>580</ps>
<kw>426</kw>
<umin>6250</umin>
</motor>
<price>108000</price>
</data>
</car>
</garage>
I want to count all cars, that are from japan AND got at least 100 ps (ps means horsepower in german). In the example above the result should be 1, because only the mx5 matches both conditions. I tried "and", I tried "intersect" and now I'm out. Could someone help me out, PLEASE!!!!!!
Here you go:
/garage/car[data/country = 'Japan' and data/motor/ps >= 100]
or:
/garage/car[data/country = 'Japan'][data/motor/ps >= 100]
or:
/garage/car[data[country = 'Japan'][motor/ps >= 100]]
The above are all equivalent. To get the count, wrap any of the above with count(...).

Resources