Search for value in element with specific attribute - xquery

<elemA>
<elemZ mytype ="1">
<myval>100</myval>
</elemZ>
<elemZ mytpe ="2">
<myval>200</myval>
</elemZ>
</elemA>
Using cts:queries, I would want to find myval of 100 in elemZ with mytype = "1". I do not see any cts query that allows cts:element-query and also filtering on attribute. Even an cts:and-query does not appear helpful.
Without attribute constraint, element-value-query and two element-queries would work easy.
cts:search(doc(), (some cts query?))

First try this simple xpath -- validate that it works, and that its not sufficiently performant for you.
//elemZ[#mytype=1]/myval[. = "100" ]
That should return myval element children of elemZ with mytype=1 and myval text content = "100"
To do better (with cts:query) will need those 'dreaded' other cts:queries and possibly some range indexes.
Roughly : (untested)
search(doc(),
cts:element-query(xs:QName("elemZ"),
cts:and-query((
cts:element-attribute-value-query(xs:QName("elemZ"), xs:QName("mytype"), "1"),
cts:element-value-query(xs:QName("myval"), "100") )) ) )
Recommend you start with the simplest expression that does anything then one by one add constraints.
In your case, it's conceivable that the query optimizer will optimize the simple xpath into the appropriate cts query. Worth trying and measuring performance. I personally like to start with a basic xpath and then only work my way up to a cts:query as needed.

Related

is it the same to use MATCHES (* + "" + *) and no parameters in a FOR EACH in Progress 4GL?

So I made the following FOR EACH
FOR EACH insp_cd
WHERE insp_cd.status_ = 1
AND insp_cd.item MATCHES('*' + pc-itemPost + '*')
AND insp_cd.update_at < NOW:
So, when the pc-itemPost is "", should I avoid using the MATCHES? Like:
IF pc-itemPost = "" THEN DO:
FOR EACH insp_cd
WHERE insp_cd.status_ = 1
AND insp_cd.update_at < NOW:
...
END.
ELSE DO:
FOR EACH insp_cd
WHERE insp_cd.status_ = 1
AND insp_cd.item MATCHES('*' + pc-itemPost + '*')
AND insp_cd.update_at < NOW:
I know it's very slow because of the table scan, but I'd like to know if there is any difference. Thanks.
Any time that you can avoid MATCHES you should do so.
Using an IF statement to choose branches that execute different static FOR EACH statements is one way to do it. Building dynamic queries based on similar logic would be another approach.
Whether or not your two queries are "different"? Sure, they are different. They have different WHERE clauses so their specific behavior (and performance) will depend on the index structure (which we don't know).
insp_cd.item matches “*” + pc-itempost + “*”
Can be very different from:
insp_cd.item = “”.
And logically it is not the same as omitting a check of insp_cd.item altogether. Logically maybe you’re attempting to exclude empty values? I’m not sure what the requirement is here.
If insp_cd.item is the first component of an index, or the second component after insp_cd.Status then a variation of this query using ‘ = “” ‘ will be much more efficient than one using MATCHES.
Back to avoiding MATCHES, at a high level:
If there is no need for wild cards use "=". Equality matches are always preferred.
If the wild card is at the end of the string use BEGINS.
If the wild card is being used to signify a known list use a series of OR clauses or a LOOKUP() or build a temp-table to join in the query.
There are probably more ways to avoid MATCHES but these are the ones that spring to mind.

Restrict the expansion of search for cts:element-value-search- Marklogic

I am using cts:element-value-search to search document based on the parameter. While the query works fine I want it to search elements in immediate children only.
For Ex. My document tree looks like this
document1.xml
<Person>
<FirstName>Johnson</FirstName>
<LastName>W</LastName>
<EmailAddress>john#abc.com</EmailAddress>
<Neighbour>
<FirstName>Mathew</FirstName>
<LastName>Long</LastName>
</Neighbour>
</Person>
document2.xml
<Person>
<FirstName>Mathew</FirstName>
<LastName>W</LastName>
<EmailAddress>john#abc.com</EmailAddress>
<Neighbour>
<FirstName>Anderson</FirstName>
<LastName>Long</LastName>
</Neighbour>
</Person>
and my query is
cts:search(
/Person,
cts:and-query((
cts:element-value-query(xs:QName("FirstName"), concat('*', "son", '*'),
("wildcarded", "case-insensitive", "whitespace-sensitive", "punctuation-sensitive"))
)),
()
)
This returns both the documents because for the first document it matches <FirstName>Johnson</FirstName>
and for the second document it matches
<FirstName>Anderson</FirstName>
which is at the lower level.
I do not want the second result and want the query to search at level 1 only.
Any help is appreciated.
You can scope sub-queries to particular container elements or properties, using cts:element-query, and cts:json-property-scope-query. Those will trim down sub-query matches to a particular ancestor.
cts:element-query(xs:QName('Person'), cts:element-value-query(xs:QName('Firstname', ...)) will not be enough however, as Neighbour/Firstname is also a descendant.
Simplest option is to use a path range index on Person/Firstname. That is by far the most straight-forward solution here.
HTH!

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?

In Classic ASP, how do I reference a variable based on a string?

I have many constant variables for example:
Const Total_US_Price = "100"
However, in my code, I am pulling a string "Total_US_Price". I want to replace my string with the value of the Const variable.
How do I do that? How do I change "Total_US_Price" to return "100" (both being strings)?
It sounds like you want to use the eval() function...
http://www.devguru.com/technologies/vbscript/QuickRef/eval.html
EDIT: I hope you're not pulling these strings from the client side (query string, POSTed form values, or cookies) or else you are opening yourself up for a world of hurt. Someone could inject whatever strings they wanted and it will get executed on your web server.
Not sure what you exactly mean with 'pulling a string' but please don't abuse eval. Use a lookup table for lookup values.
set x = createobject("scripting.dictionary")
x("total_us_price") = 100
price = x("total_us_price")
You would use the "CInt " function.
VBScript CInt Function explained and usage.
I don't think this can be done other than by executing code dynamically, using either Eval() to get the value directly, or Execute() to get it by side-effect.
Specifically:
MyVarName = "Total_US_Price"
Value100 = Eval(MyVarName)
' Or...
Exectute("Value100 = " + MyVarName)
The Eval is more practical, but less flexible...

Resources