开发者

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)), ' '),
          '&#xA;'
         )
      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, '&#xA;')
      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)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜