number inside [] after in orderby in XQuery - xquery

Following is the query
for $x in $books
where $x/price>=38
order by ($x/price)[l]
return ($x/title, $x/price)
what is denoted by [1] located after order by($x/price)?

It looks to me like a lower-case-L rather than a digit-one.
If it's really a one [1] then it means select the first item in the sequence $x/price. I suspect each book has only one price, in which case it's completely redundant.

Related

How do I get a list of all elements and their attributes via XQuery

I am quite new to XQuery and I am trying to get a list of all elements and all attributes.
It should look like this:
element1 #attributex, #attribue y, ...
element 2 #attribute x, #attribute y, ...
element 3 #attribute x, #attribute y, ...
I am trying this so far, but the error "Item expected, sequence found":
for $x in collection("XYZ")
let $att := local-name(//#*)
let $ele := local-name(//*)
let $eleatt := string-join($ele, $att)
return $eleatt
I feel like I am turning an easy step into a complicated one. Please help.
Thanks in advance, Eleonore
//#* gives you a sequence of attribute nodes, //* a sequence of element nodes. In general to apply a function like local-name() to each item in a sequence, for nodes you have three options:
Use a final step /local-name() e.g. //#*/local-name() or //*/local-name()
In XQuery 3.1 use the map operator ! e.g. //#*!local-name()
Use a for .. return expression e.g. for $att in //#* return local-name($att)
The local-name() function takes a single node as its argument, not a sequence of nodes. To apply the same function to every node in a sequence, using the "!" operator: //*!local-name().
The string-join() function takes two arguments, a list of strings, and a separator. You're trying to pass two lists of strings. You want
string-join((//*!local-name(), //#*!local-name()), ',')
Of course you might also want to de-duplicate the list using distinct-values(), and to distinguish element from attribute names, or to associate attribute names with the element they appear on. That's all eminently possible. But for that, you'll have to ask a more precise question.

searching in multiple collections joined by common fileds in xquery marklogic

I have two collections('A' and 'B') with millions of transport insurance data documents. The two collections have four elements in common(customer-no, date-of-insurance, insurance-no,accident-number) and one element(license-no) exists only in one collection('A'). I want to extract all the documents that are present in both the collections and also have the element of collection'A'. I am able to retrieve all the customer-nos from 'A' with cts-search. Then I loop through each of these customer-nos to look for license-no in 'A'. It gives an empty sequence. But I know this is not possible. Could someone guide me with appropriate search logic?
let $col-A := cts:search(
doc(),
cts:and-query((
cts:collection-query('col-A'),
cts:element-value-query(xs:QName('abc:Acusno'), '*', (("wildcarded")))
)))
for $each in $col-A
let $col-B := cts:search(doc(),
cts:and-query((cts:collection-query('col-B'),
cts:element-value-query(xs:QName('abc:Bcusno'), $each)
)))
return $col-B
returns empty sequence
Your first cts:search is returning entire documents, which you are then passing in as argument into the value-query. You probably want to pass in just the value of abc:Acusno. You could do that with something like $each//abc:Acusno.
Your code is not using a very efficient approach though, and what if certain Acusno values occur multiple times?
I would recommend putting a range index on abc:Acusno, and using cts:values to pull up the unique values that match a given query. Then feed that entire list as one argument without any looping to a query against abc:Bcusno. You don't have to use a range index, and range query on Bcusno, but it could be useful to have that index anyhow. The code would then look something like this:
let $query :=
cts:and-query((
cts:collection-query('col-A'),
cts:element-query(xs:QName('abc:Acusno'), cts:true-query())
))
let $customerNrs :=
cts:values(
cts:element-reference(xs:QName("abc:Acusno")),
(),
(),
$query
)
return cts:search(
collection(),
cts:and-query((
cts:collection-query('col-B'),
cts:element-range-query(xs:QName('abc:Bcusno'), '=', $customerNrs)
))
)
Note: be careful when returning full search lists like this. You might want to paginate the response.
HTH!

Xquery - How to match two sequences within a quantifier expression

Like many, I'm tackling the Mondial database on XML. It would be a piece of cake, if XQuery syntax wasn't doing its best to sabotage.
let $inland := //province/#id
where every $sea in //sea satisfies
$sea/located/#province != $inland
return $inland
What I am trying to do in the above is find all "inland" provinces, the provinces that don't have a sea next to it. This, however, doesn't work, because the $sea/located/province is a big string, with every single province that it borders in it.
So I tried to modify into.
let $inland := //province/#id
where every $sea in //sea satisfies
not(contains($sea/located/#province, $inland))
return $inland
Where I would like it to only find the provinces that are a part of the sea's bordering provinces. Simple and straightforward.
Error message:
Stopped at C:/Users/saffekaffe/Desktop/mondial/xml/country_without_island.xml, 2/1:
[XPTY0004] Item expected, sequence found: (attribute id {"prov-Greece-2"},....
How do I get around this?
Example of //sea/located/province#
province="prov-France-5 prov-France-20 prov-France-89 prov-France-99"
Example of //province/#id
id="prov-Greece-2"
There are multiple ways in which XQuery works in a different way than you seem to expect.
The comparison operators = and != have existential semantics if at least one of their arguments is a sequence instead of a single item. This means that $seq1 = $seq2 is equivalent to some $x in $seq1, $y in $seq2 satisfies $x = $y. The query ('foo', 'bar') = ('bar', 'baz', 'quuz') returns true because there is at least one common item.
An XQuery exception like //province/#id evaluates to a sequence of all matching nodes. In your case that would be a sequence of over 1000 province IDs: (id="prov-cid-cia-Greece-2", id="prov-cid-cia-Greece-3", id="prov-cid-cia-Greece-4", [...]). This sequence is then bound to the variable $inland in your let clause. Since you don't iterate over individual items in $inland (for example using a for clause), the where condition then works on the whole sequence of all provinces worldwide at once. So your condition every $sea in //sea satisfies
$sea/located/#province != $inland now means:
"For every sea there is a province located next to it that has an #id that is not equal to at least one of all existing province IDs."
Th is returns false because there are seas with no located children, e.g.the Gulf of Aden.
contains($str, $sub) is not a good fit for checking if a substring is contained in a space-delimited string, because it also matches parts of entries: contains("foobar baz quux", "oob") returns true.
Instead you should either split the string into its parts using tokenize($str) and look through its parts, or use contains-token($str, $token).
Putting it all together, a correct query very similar to your original one is:
for $inland in //province/#id
where
every $sea in //sea
satisfies not(contains-token($sea/located/#province, $inland))
return $inland
Another approach would be to first gather all (unique) provinces that are next to seas and then return all provinces not in that sequence:
let $next-to-sea := distinct-values(//sea/located/#province/tokenize(.))
return //province/#id[not(. = $next-to-sea)]
Even more compact (but potentially less efficient):
//province/#id[not(. = //sea/located/#province/tokenize(.))]
On the other end of the spectrum you can use XQuery 3.0 maps to replace the potentially linear search through all seaside provinces by a single lookup:
let $seaside :=
map:merge(
for $id in //sea/located/#province/tokenize(.)
return map{ $id: () }
)
return //province/#id[not(map:contains($seaside, .))]

Compare two elements of the same document in MarkLogic

I have a MarkLogic 8 database in which there are documents which have two date time fields:
created-on
active-since
I am trying to write an Xquery to search all the documents for which the value of active-since is less than the value of created-on
Currently I am using the following FLWOR exression:
for $entity in fn:collection("entities")
let $id := fn:data($entity//id)
let $created-on := fn:data($entity//created-on)
let $active-since := fn:data($entity//active-since)
where $active-since < $created-on
return
(
$id,
$created-on,
$active-since
)
The above query takes too long to execute and with increase in the number of documents the execution time of this query will also increase.
Also, I have
element-range-index for both the above mentioned dateTime fields but they are not getting used here. The cts-element-query function only compares one element with a set of atomic values. In my case I am trying to compare two elements of the same document.
I think there should be a better and optimized solution for this problem.
Please let me know in case there is any search function or any other approach which will be suitable in this scenario.
This may be efficient enough for you.
Take one of the values and build a range query per value. This all uses the range indexes, so in that sense, it is efficient. However, at some point, there is a large query that us built. It reads similiar to a flword statement. If really wanted to be a bit more efficient, you could find out which if your elements had less unique values (size of the index) and use that for your iteration - thus building a smaller query. Also, you will note that on the element-values call, I also constrain it to your collection. This is just in case you happen to have that element in documents outside of your collection. This keeps the list to only those values you know are in your collection:
let $q := cts:or-query(
for $created-on in cts:element-values(xs:QName("created-on"), (), cts:collection-query("entities"))
return cts:element-value-range-query(xs:Qname("active-since"), "<" $created-on)
)
return
cts:search(
fn:collection("entities"),
$q
)
So, lets explain what is happening in a simple example:
Lets say I have elements A and B - each with a range index defined.
Lets pretend we have the combinations like this in 5 documents:
A,B
2,3
4,2
2,7
5,4
2,9
let $ := cts:or-query(
for $a in cts:element-values(xs:QName("A"))
return cts:element-value-range-query(xs:Qname("B"), "<" $a)
)
This would create the following query:
cts:or-query(
(
cts:element-value-range-query(xs:Qname("B"), "<" 2),
cts:element-value-range-query(xs:Qname("B"), "<" 4),
cts:element-value-range-query(xs:Qname("B"), "<" 5)
)
)
And in the example above, the only match would be the document with the combination: (5,4)
You might try using cts:tuple-values(). Pass in three references: active-since, created-on, and the URI reference. Then iterate the results looking for ones where active-since is less than created-on, and you'll have the URI of the doc.
It's not the prettiest code, but it will let all the data come from RAM, so it should scale nicely.
I am now using the following script to get the count of documents for which the value of active-since is less than the value of created-on:
fn:sum(
for $value-pairs in cts:value-tuples(
(
cts:element-reference(xs:QName("created-on")),
cts:element-reference(xs:QName("active-since"))
),
("fragment-frequency"),
cts:collection-query("entities")
)
let $created-on := json:array-values($value-pairs)[1]
let $active-since := json:array-values($value-pairs)[2]
return
if($active-since lt $created-on) then cts:frequency($value-pairs) else 0
)
Sorry for not having enough reputation, hence I need to comment here on your answer. Why do you think that ML will not return (2,3) and (4,2). I believe we are using an Or-query which will take any single query as true and return the document.

SELECT COUNT(*) doesn't work in QML

I'm trying to get the number of records with QML LocalStorage, which uses sqlite. Let's take this snippet in account:
function f() {
var db = LocalStorage.openDatabaseSync(...)
db.transaction (
function(tx) {
var b = tx.executeSql("SELECT * FROM t")
console.log(b.rows.length)
var c = tx.executeSql("SELECT COUNT(*) FROM t")
console.log(JSON.stringify(c))
}
)
}
The output is:
qml: 3
qml: {"rowsAffected":0,"insertId":"","rows":{}}
What am I doing wrong that the SELECT COUNT(*) doesn't output anything?
EDIT: rows only seems empty in the second command. Calling
console.log(JSON.stringify(c.rows.item(0)))
gives
qml: {"COUNT(*)":3}
Two questions now:
Why is rows shown as empty
How can I access the property inside c.rows.item(0)
In order to visit the items, you have to use:
b.rows.item(i)
Where i is the index of the item you want to get (in your first example, i belongs to [0, 1, 2] for you have 3 items, in the second one it is 0 and you can query it as c.rows.item(0)).
The rows field appears empty and it is a valid result, for the items are not part of the rows field itself (indeed you have to use a method to get them, as far as I know that method could also be a memento that completely enclose the response data) and the item method is probably defined as not enumerable (I cannot verify it, I'm on the beach and it's quite difficult to explore the Qt code now :-)). You can safely rely on the length parameter to know if there are returned values, thus you can iterate over them to print them out. I did something like that in a project of mine and it works fine.
The properties inside item(0) have the same names given for the query. I suggest to rewrite that query as:
select count(*) as cnt from t
Then, you can get the count as:
c.rows.item(0).cnt

Resources