Xquery version of "NOT IN" clause not working as expected - xquery

I have an xml file which can be downloaded in the following link:
http://expirebox.com/download/483d465091802df68da10feddc1ec98c.html
I am trying to select all those movies without the styles of "live action" and "camp" in my movies.xml. To achieve this I am using the following query
for $movie in db:open("movies","movies.xml")/movies/movie
where $movie/styles/style!=("noir","anthology")
return $movie
However, all nodes in movies are getting selected. What is going wrong in my query?
Some experts are pointing out the inability of the standard not equal operator not working in this case due to the semantics of XQuery involved. However, my intention is to find a corresponding query of an SQL feature and not just understand the semantics.

!= uses set comparison logic.
For values that are not in that sequence, such as "foo", the expression "foo" != ("noir", "anthology") returns true However, the expression "noir" != ("noir", "anthology") would also return true. This is because "noir" does not equal one of the items in the sequence, "anthology", even though it also equals one of the items in the sequence as well. The expression "noir" = ("noir", "anthology") returns true because "noir" is equal to one of the items in the sequence, "noir".
Refer to the specification:
https://www.w3.org/TR/1999/REC-xpath-19991116/#booleans
NOTE: If $x is bound to a node-set, then $x="foo" does not mean the same as not($x!="foo"): the former is true if and only if some node in $x has the string-value foo; the latter is true if and only if all nodes in $x have the string-value foo.
Use:
where not($movie/styles/style=("noir","anthology"))
or:
where $movie/styles/style[not(.=("noir","anthology"))]

Related

How can I dynamically change the where conditions of a for each loop?

I have a table of records that has two logical flags, holdn and holdl. I want to loop through this table with 3 different criteria.
Either flag is TRUE - We want to see everything that is on hold
Flag holdl is TRUE - We only want see items that are on hold for this one reason
Flag holdn is TRUE - We only want to see items that are on hold for this other reason.
I cannot figure out how to dynamically change the for each loop based on this. What I have tried so far is to set the value of a variable based on these conditions and then use the content of the variable as one of the where parameters. This does not work as Progress complains that there is a data mismatch. The variable is a string, the flags are logical, so that does make sense. See sample code below. This is a snippet of the actual code with the the table name changed. The which-hold, order-from, etc variables are defined and set in a different module which calls this one.
DEFINE VARIABLE which-hold# AS CHARACTER FORMAT "x(30)" NO-UNDO.
CASE which-hold:
WHEN "B" THEN which-hold# = "(widget.holdn or widget.holdl)".
WHEN "L" THEN which-hold# = "widget.holdl".
WHEN "N" THEN which-hold# = "widget.holdn".
END CASE.
for each widget where which-hold# and
widget.order-no >= order-from and widget.order-no <= order-thru and
widget.line-no >= line-from and widget.line-no <= line-thru and
widget.joint-no >= joint-from and widget.joint-no <= joint-thru
no-lock:
A bunch of code to make a nice report with the retrieved records...
end.
Self taught Progress programmer here, who has inherited a huge, poorly documented application. Please be gentle.
If you would prefer not to deal with handles a semi-dynamic approach is also possible:
define variable i as integer no-undo.
define query q for customer.
do while true:
update i.
case i:
when 0 then quit.
when 1 then open query q for each customer no-lock where custNum >= 1000.
when 2 then open query q for each customer no-lock where state = "nh".
otherwise open query q for each customer no-lock where name begins "u".
end.
do while true with frame a:
get next q.
if not available customer then leave.
display custNum name state with frame a 10 down.
down with frame a.
end.
close query q.
end.
What you want is actually a dynamic query. I'll get to it at the end, but first I'd like to explain why you won't be able to try and substitute the field name in the which-hold# variable: because the query is evaluated at compile time. And this is what it reads (supposing which-hold# has a value of widget.holdn
FOR EACH widget where "widget-holdn" (...)
And that does not evaluate to TRUE or FALSE. So what, you ask? Well, that is the key here. Every condition needs to evaluate to true or false, so you'd be more in luck if you try
for each widget where (if widget-hold# = 'widget.holdn' then widget.holdn = true else TRUE) (...)
Again, notice the condition will exist if widget-hold# has the value I want, otherwise it doesn't filter on this at all.
So you can just code the way I showed (for each of the conditions you have) and it should work fine.
BUT let me suggest a dynamic query instead.
You need to have:
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
CREATE QUERY hQuery.
hQuery:SET-BUFFERS(BUFFER widget:HANDLE).
hQuery:QUERY-PREPARE('<THIS IS THE CORE>').
hQuery:QUERY-OPEN().
DO WHILE hQuery:GET-NEXT():
A bunch of code to make a nice report with the retrieved records...
END.
So in the core you have a string that corresponds to your for each the way you want it to look. So it should be for example (store this in a variable, or assemble it inside the query prepare, it doesn't matter):
'FOR EACH widget NO-LOCK WHERE ' +
(if which-hold = 'B' then 'widget.holdn = true and widget.holdl = true'
else if which-hold = 'L' then 'widget-holdl = true'
else /* N */ 'widget-holdn = true').
Remember I said your query is evaluated at compile time? Well, just so you know, dynamic queries on the other end are evaluated at run time, so be prepared for errors to pop up only when you run. Another thing I should mention is dynamic queries are slower than static ones, so please evaluate and choose your poison :)
This should be what you need. Please let me know if it's helpful or any questions remain.

Xquery Where clause not working when using nested query

Good Day,
I'm new to xquery.
I'm trying to execute xquery with a where clause that returns the values greater than a value returned from a nested query as show below. It runs but returns values that are not greater than the returned value
If I use the where clause directly with the value it works fine. I'm using BaseX to execute my query. Appreciate any feedback, I believe the parser may be reading my subquery wrong.
for $y in doc("url.xml")/taxi_stations/stand
where $y/taxis>=
(
for $x in doc("url.xml")/taxi_stations/stand
where $x/name="Jacksonville"
return data($x/taxis)
)
return ($y/taxis,$y/name)
What is the value of $taxis? It's possible your comparisons are happening on strings, not numbers.
In your nested return, you call data() on $x/taxis - assuming that correctly returns a number value, then $y/taxis in your outer where clause should probably also be wrapped in data().
However, if there is no schema on your document, then data() will simply return a string. In that case you should convert your taxis element to a number using fn:number() or directly casting it like $y/taxis/xs:integer(.).

An XDMP-NOTANODE error using xquery in marklogic

I'm getting the XDMP-NOTANODE error when I try to run an XQuery in MarkLogic. When I loaded my xml documents I loaded meta data files with them. I'm a student and I don't have experience in XQuery.
error:
[1.0-ml] XDMP-NOTANODE: (err:XPTY0019) $article/article/front/article-meta/title-group/article-title -- xs:untypedAtomic("
") is not a node
Stack Trace
At line 3 column 77:
In xdmp:eval("(for $article in fn:distinct-values(/article/text()) &#1...", (), <options xmlns="xdmp:eval"><database>4206169969988859108</database> <root>C:\mls-projects\pu...</options>)
$article := xs:untypedAtomic("
")
1. (for $article in fn:distinct-values(/article/text())
2.
3. return (fn:distinct-values($article/article/front/article-meta/title-group/article-title)
4.
5.
Code:
(
for $article in fn:distinct-values(/article/text())
return (
fn:distinct-values($article/article/front/article-meta/title-group/article-title/text())
)
)
Every $article is bound to an atomic value (fn:distinct-values() returns a sequence of atomic values). Then you try to apply a path expression (using the / operator) on $article. Which is forbidden, as the path operator requires its LHS operator to be nodes.
I am afraid your code does not make sense enough for me to suggest you an actual solution. I can only pinpoint where the error is.
Furthermore, using text() at the end of a path is most of the time a bad idea. And if /article is a complex document, it is certainly not what you want. One of the text nodes you select (most likely the first one) is simply one single newline character.
What do you want to achieve?
Your $article variable is bound to an atomic value, not a node() from the article document. You can only use an XPath axis on a node.
When you apply the function distinct-values() in the for statement, it returns simple string values, not the article document or nodes from it.
You can probably make things work by using the values in a predicate filter like this:
for $article-text in fn:distinct-values(/article/text())
return
fn:distinct-values(/article[text()=$article-text]/front/article-meta/title-group/article-title/text())
Note: The above XQuery should avoid the XDMP-NOTANODE error, but there are likely easier (and more efficient) solutions for achieving your goal. If you were to post a sample of your document and describe what you are trying to achieve, we could suggest alternatives.
Bit of a wild guess, but you have two distinct-values in your code. That makes me think you want a unique list of articles, and then finally a unique list of article-title's. I would hope you already have unique articles in your database, unless you are explicitly attempting to de-duplicate them.
In case you just want the overall unique list of article titles, I would do something like:
distinct-values(
for $article in collection()/article
return
$article/front/article-meta/title-group/article-title
)
HTH!

Marklogic collate sequence in XQuery

Is there a way to modify the elements a sequence so only collated versions of the items are returned?
let $currencies := ('dollar', 'Dollar', 'dollar ')
return fn:collated-only($currencies, "http://marklogic.com/collation/en/S1/T00BB/AS")
=> ('dollar', 'dollar', 'dollar')
The values that are stored in the range index (that feeds the facets) are literally the first value that was encountered that compared equal to the others. (Because, the collation says you don't care...)
You can get a long way by calling
fn:replace(fn:lower-case(xdmp:diacritic-less(fn:normalize-unicode($str,"NFKC"))),"\p{P}","")
This won't be exactly the same in that it overfolds some things and underfolds others, but it may be good for your purposes.
Is this the expected output? There is no fn:collated-only function, so I'm assuming you're asking how to write such a function or whether there is such a function.
The thing is, there isn't a mapping from one string to another in collation comparisons, there is only a comparison algorithm (the Unicode Collation Algorithm) so there really is no canonical kind of string to return to you, and therefore no API to do so.
Stepping back, what is the problem you are actually trying to solve? By the rules of that collation, "dollar" and "Dollar" are equivalent, and by using it you declare you don't care which form you use, so you could use either one.
If these values are in XML elements and you have a range index using http://marklogic.com/collation/en/S1/T00BB/AS, you can do something like this:
let $ref := cts:element-reference(xs:QName("currency"), "collation=http://marklogic.com/collation/en/S1/T00BB/AS")
for $curr in cts:values($ref, (), "frequency-order")
return $curr || ": " || cts:frequency($curr)
This will produce results like:
"dollar: 15",
"euro: 12"
... and so on. The collation will disregard the differences among your sample inputs. These results could be formatted however you want. Is that what you're looking to do?

TALES expression to compare numeric input in Plone?

TALES expression is new to me. Can I get some good reference for the same? Actually I wish to define a content rule for numeric input field using ploneformgen. Something like:
python: request.form.get('amt', False) <= 5000
then apply the rule.
Here 'amt' is a numeric/whole number field on the input form.
For reference, you should look at the official TALES specification, or refer to the TALES section of the Zope Page Templates reference.
In this case, you are using a plain python expression, and thus the normal rules of python code apply.
The expression request.form.get('amt', False) would return the request parameter 'amt' from the request, and if that's missing, return the boolean False, which you then compare to an integer value.
There are 2 things wrong with that expression: first of all you assume that the 'amt' parameter is an integer value. Even a PFG integer field however, is still a string in the request object. As such you'll need to convert in to an integer first before you can compare it.
Also, you fall back to a boolean, which in integer comparisons will be regarded as the equivalent of 0, better be explicit and use that instead:
python: int(request.form.get('amt', 0)) <= 5000
Note that for a PFG condition, you can also return a string error message instead of boolean True:
python: int(request.form.get('amt', 0)) <= 5000 or 'Amount must be not be greater than 5000'
Usually form parameters are passed in as strings if they are not defined on the application level otherwise e.g.
Zope will under the hood use the fieldname amt:int in order to convert the value to an integer.
So you may want to try to put an int(....) around the first expression.

Resources