Advanced XPath query
I have an XML file that looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<PrivateSchool>
<Teacher id="teacher1">
<Name>
teacher1Name
</Name>
</Teacher>
<Teacher id="teacher2">
<Name>
teacher2Name
</Name>
</Teacher>
<Student id="student1">
<Name>
student1Name
</Name>
</Student>
<Student id="student2">
<Name>
student2Name
</Name>
</Student>
<Lesson student="student1" teacher="teacher1" />
<Lesson student="student2" teacher="teacher2" />
<Lesson student="student3" teacher="teacher3" />
<Lesson student="student1" teacher="teacher2" />
<Lesson student="student3" teacher="teacher3" />
<Lesson student="student1" teacher="teacher1" />
<Lesson student="student2" teacher="teacher4" />
<Lesson student="student1" teacher="teacher1" />
</PrivateSchool>
There's also a开发者_开发百科 DTD associated with this XML, but I assume it's not much relevant to my question. Let's assume all needed teachers and students are well defined.
What is the XPath query that returns the teachers' NAMES, that have at least one student that took more than 10 lessons with them?
I was looking at many XPath sites/examples. Nothing seemed advanced enough for this kind of question.
Doing a complex join in a single XPath may be possible, but you're banging your head against a brick wall. XQuery or XSLT are much more suited to this kind of thing. Here it is in XQuery:
declare variable $doc as doc('data.xml');
declare function local:numLessons($teacher, $student) {
return count($doc//Lesson[@teacher = $teacher and @student = $student])
};
$doc//Teacher[some $s in //Lesson/@student satisfies local:numLessons(@id, $s) gt 10]/Name
Having done that, if you are really determined you can reduce it to XPath 2.0:
doc('data.xml')//Teacher[
for $t in . return
some $s in //Lesson/@student satisfies
count(//Lesson[@teacher = $t and @student = $s]) gt 10] /Name
Not tested.
This is an XPath 2.0 solution:
(/PrivateSchool
/Lesson)
[index-of(
/PrivateSchool
/Lesson
/concat(@student, '|', @teacher),
concat(@student, '|', @teacher)
)[10]
]/(for $teacher in @teacher
return /PrivateSchool
/Teacher[@id = $teacher]
/Name)
Use this XPath 2.0 expression:
for $limit in 2,
$t in /*/Teacher,
$id in $t/@id,
$s in /*/Student/@id,
$numLessons in
count(/*/Lesson[@teacher eq $id
and @student eq $s])
return
if($numLessons gt $limit)
then
(string-join(($t/Name, $s, xs:string($numLessons)), ' '),
'
'
)
else ()
here I have set $limit
to 2, so that when this XPath expression is evaluated against the provided XML document:
<PrivateSchool>
<Teacher id="teacher1">
<Name>teacher1Name</Name>
</Teacher>
<Teacher id="teacher2">
<Name>teacher2Name</Name>
</Teacher>
<Student id="student1">
<Name>student1Name</Name>
</Student>
<Student id="student2">
<Name>student2Name</Name>
</Student>
<Lesson student="student1" teacher="teacher1" />
<Lesson student="student2" teacher="teacher2" />
<Lesson student="student3" teacher="teacher3" />
<Lesson student="student1" teacher="teacher2" />
<Lesson student="student3" teacher="teacher3" />
<Lesson student="student1" teacher="teacher1" />
<Lesson student="student2" teacher="teacher4" />
<Lesson student="student1" teacher="teacher1" />
</PrivateSchool>
it produces the correct result:
teacher1Name student1 3
In your real expression you'll have $limit
set to 10
and will only return the teachers' names:
for $limit in 10,
$t in /*/Teacher,
$id in $t/@id,
$s in /*/Student/@id,
$numLessons in
count(/*/Lesson[@teacher eq $id
and @student eq $s])
return
if($numLessons gt $limit)
then ($t/Name, '
')
else ()
The solution posted by Michael Kay for xpath 2.0 is correct, but aproximate. An exact solution for the xml posted at the question would be (without absolute paths):
//Teacher[
for $t in . return
some $s in //Student satisfies
count(//Lesson[@teacher = $t/@id and @student = $s/@id]) gt 1
]/Name
(I used "gt 1" instead of "gt 10" in order to get some result)
精彩评论