简体   繁体   中英

xquery to return two elements with two predicates?

I'm trying to get a list of names back from this - but it's just giving me back "true" - what am i doing wrong ?

for $x in /Book
where $x//Occupation/text()="Builder" and $x//Sex/text()="M"
return $x//Name/text() and $x//LastName/text()

Where am i going wrong?

Sample of XML : ` .....

<Person>

   <Name>Mathew</Name>

   <LastName>Michaels</LastName>

   <Relation>Father</Relation>

   <Religion>Roman Catholic</Religion>

   <Education>Read</Education>

   <Age>83</Age>

   <Sex>M</Sex>

   <Occupation>Builder</Occupation>

   <Maried>Widower</Maried>

   <PlaceOfBirth>Co. Kildare</PlaceOfBirth>

</Person>        `

You're returning a boolean value (true or false) because you're using a boolean expression ( X and Y ). If you want to return the union of two node sequences, use a pipe symbol ( X | Y ) instead, or if you want a sequence where X comes before Y, use a comma ( X,Y ). Or if you want to return a concatenation of two strings, use the concat() function (as in my example further below).

I think perhaps you're not understanding how "for" expressions work. The $x variable is bound to whatever sequence of nodes is returned by the expression following " in ". So in your case, $x is bound to one <Book> element (or, depending on your XQuery implementation, maybe all <Book> document elements in the database). Since $x is bound to <Book> , that means your query is essentially saying this (if you first make the above correction, replacing "and" with "|"): "If this <Book> document has any <Occupation> elements with "Builder" and any <Sex> elements with "M", then return all of the <Book> doc's <Name> and <LastName> text node children.

So it looks like what you really want is to bind $x to <Person> elements instead (assuming <Person> appears as a child of <Book> , which your sample XML didn't specify):

for $x in /Book/Person
where $x/Occupation eq "Builder" and $x/Sex eq "M"
return concat($x/Name,' ',$x/LastName)

Notice I made a few changes. I removed the "//" in favor of "/", because your sample XML shows that <Occupation> , <Sex> , <Name> , and <LastName> are children of <Person> . Only use "//" if you don't know where the element might appear (n-levels deep), for two reasons:

  1. "//" is slower than "/", because it means the XQuery engine has to search that many more places, and
  2. your code implies the wrong thing: that these elements can appear as descendants (and don't necessarily always appear as direct children).

I also added the concat() function, because I suspected that's close to what you're trying to extract.

I also got rid of the text() node tests. In general, you don't need them (and should avoid them). What you really want to do is compare the string-values of each element, not the string-values of each text node child. You may know that each element will only have one text node child, but there's no reason to write code that will break in situations like this:

<Religion>Roman <!--comment-->Catholic</Religion>

or

<PlaceOfBirth><b>Co.</b> Kildare</PlaceOfBirth>

I also replaced "=" with "eq", because "=" is for comparing possibly plural sequences and "eq" is only for comparing singular sequences.

Finally, you don't even need a FLWOR expression for this use case. You could also write it as a path expression:

/Book/Person[Occupation eq "Builder"][Sex eq "M"]/concat(Name,' ',LastName)

"and" is a boolean operator - the result is always true or false. Whereas in English we might use "and" to get the union of two sets ("give me the book's author and title"), in logic 'and' always combines two booleans (so author and title is true if both author and title exist). So your "and" operator needs to be replaced by a "union" operator, or something else which combines the results, such as concat() or ",".

The other problem is operator precedence. Generally, if the return clause includes any non-trivial expression, it's safest to put parens around it. You want

for $x in X return (a, b)

not

(for $x in X return a), b

The expressions specified after the "return" clause are combined in a logical operation, which always returns true or false . The following two versions will probably do what you want:

for $x in /Book
where $x//Person/text()="Builder" and $x//Place/text()="F"
return ($x//Name/text(), $x//LastName/text())

for $x in /Book
where $x//Person/text()="Builder" and $x//Place/text()="F"
return ($x//Name/text() | $x//LastName/text())

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM