XQuery: Calculate the diffrence of two days - xquery

I was looking for a way to calculate the duration between 2 xs:date objects.
I got a working solution using the div approach that I found here
Im not happy with that solution. Maybe there is a better approach doing it...
let $today := current-date()
let $date:= xs:date("2019-05-20")
(: will return 3 :)
return ((xs:dayTimeDuration($today - $date) div xs:dayTimeDuration("PT1M")) div 60) div 24
Basically I get the minutes passed by and than calculate the hours from the diffrence, which will be used to calculate the days that passed by.

To compute the difference in days, it should suffice to compute the difference between the two xs:dates and then you can divide that result by a duration of one day:
let $today := current-date()
let $date:= xs:date("2019-05-20")
return ($today - $date) div xs:dayTimeDuration("P1D")
https://xqueryfiddle.liberty-development.net/pPgCcoA

Related

Need to get the difference in milliseconds between two different timestamps using XQuery in MarkLogic

I need to add milliseconds into the timestamp and need to get the difference in milliseconds.
My Code:
xquery version "1.0-ml";
let $date1 := xs:dateTime("2022-01-17T21:45:00")
let $date2 := xs:dateTime("2022-01-17T22:45:00")
let $result := fn:abs(($date1 - $date2) div xs:dayTimeDuration("PT1S"))
return $result * 1000 (:Convert sec to ms:)
If you want the duration presented in milliseconds, then divide by xs:dayTimeDuration('PT0.001S')
($date1 - $date2) div xs:dayTimeDuration('PT0.001S')
and if you want it as a positive number, then apply abs() as you were before:
abs(($date1 - $date2) div xs:dayTimeDuration('PT0.001S'))

How can I round a chrono::Datetime to the nearest second?

I want to get the current time rounded to the nearest second using the chrono crate but I don't know how to strip or round the result of
chrono::UTC.now().
It doesn't seem like there are any operations to modify an existing `DateTime.
chrono::UTC.now()
Returns: 2019-05-22T20:07:59.250194427Z
I want to get: 2019-05-22T20:07:59.000000000Z
How would I go about doing that in the most efficient way without breaking up the DateTime value into its components and recreating it?
Use the round_subsecs method with 0 as an argument.
use chrono::prelude::*;
fn main() {
let utc: DateTime<Utc> = Utc::now().round_subsecs(0);
println!("{}", utc);
}
The result is:
2019-05-22 20:50:46 UTC

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.

Removing consecutive numbers from a sequence in XQuery

XQuery
Input: (1,2,3,4,5,6,7,14,15,16,17,24,25,26,27,28)
Output: (1,7,14,17,24,28)
I tried to remove consecutive numbers from the input sequence using the XQuery functions but failed doing so
xquery version "1.0" encoding "utf-8";
declare namespace ns1="http://www.somenamespace.org/types";
declare variable $request as xs:integer* external;
declare function local:func($reqSequence as xs:integer*) as xs:integer* {
let $nonRepeatSeq := for $count in (1 to count($reqSequence)) return
if ($reqSequence[$count+1] - $reqSequence) then
remove($reqSequence,$count+1)
else ()
return
$nonRepeatSeq
};
local:func((1,2,3,4,5,6,7,14,15,16,17,24,25,26,27,28))
Please suggest how to do so in XQuery functional language.
Two simple ways to do this in XQuery. Both rely on being able to assign the sequence of values to a variable, so that we can look at pairs of individual members of it when we need to.
First, just iterate over the values and select (a) the first value, (b) any value which is not one greater than its predecessor, and (c) any value which is not one less than its successor. [OP points out that the last value also needs to be included; left as an exercise for the reader. Or see Michael Kay's answer, which provides a terser formulation of the filter; DeMorgan's Law strikes again!]
let $vseq := (1,2,3,4,5,6,7,14,15,16,17,24,25,26,27,28)
for $v at $pos in $vseq
return if ($pos eq 1
or $vseq[$pos - 1] ne $v - 1
or $vseq[$pos + 1] ne $v + 1)
then $v
else ()
Or, second, do roughly the same thing in a filter expression:
let $vseq := (1,2,3,4,5,6,7,14,15,16,17,24,25,26,27,28)
return $vseq[
for $i in position() return
$i eq 1
or . ne $vseq[$i - 1] + 1
or . ne $vseq[$i + 1] - 1]
The primary difference between these two ways of performing the calculation and your non-working attempt is that they don't say anything about changing or modifying the sequence; they simply specify a new sequence. By using a filter expression, the second formulation makes explicit that the result will be a subsequence of $vseq; the for expression makes no such guarantee in general (although because for each value it returns either the empty sequence or the value itself, we can see that here too the result will be a subsequence: a copy of $vseq from which some values have been omitted.
Many programmers find it difficult to stop thinking in terms of assignment to variables or modification of data structures, but its worth some effort.
[Addendum] I may be overlooking something, but I don't see a way to express this calculation in pure XPath 2.0, since XPath 2.0 seems not to have any mechanism that can bind a variable like $vseq to a non-singleton sequence of values. (XPath 3.0 has let expressions, so it's not a challenge there. The second formulation above is itself pure XPath 3.0.)
In XSLT this can be done as:
<xsl:for-each-group select="$in" group-adjacent=". - position()">
<xsl:sequence select="current-group()[1], current-group()[last()]"/>
</xsl:for-each-group>
In XQuery 3.0 you can do it with tumbling windows, but I'm too lazy to work out the detail.
An XPath 2.0 solution (assuming the input sequence is in $in) is:
for $i in 1 to count($in)
return $in[$i][not(. eq $in[$i - 1]+1 and . eq $in[$i+1]-1)]
There are several logic and XQuery usage errors in your solution, but the main problem with it is that variables in XQuery are immutable, so you cannot reassign a value to one once assigned. Therefore, it's often easier to think about these types of problems in terms of recursive solutions:
declare function local:non-consec(
$prev as xs:integer?,
$rest as xs:integer*
) as xs:integer*
{
if (empty($rest)) then ()
else
let $curr := head($rest)
let $next := subsequence($rest, 2, 1)
return (
if ($prev eq $curr - 1 and $curr eq $next - 1)
then () (: This number is part of a consecutive sequence :)
else $curr,
local:non-consec(head($rest), tail($rest))
)
};
local:non-consec((), (1,2,3,4,5,6,7,14,15,16,17,24,25,26,27,28))
=>
1
7
14
17
24
28

Counting the number of elements of an array but with a condition

I have an array with real numbers, say A. I have calculated the mean as np.mean(A)
Now I want to check how many elements fell below the mean and how many above.
for example
A = [ 1 2 3 5] so the average is 2.75. So, i have two elements below the average and two elements above.
Any help will be appreciated
Not sure if this is what you are looking for, but you could do:
function mean(array){
var sum=0;
for (item in array){
sum = sum + array[item];
}
return sum/(array.length)
}
function belowMean(array) {
return array.filter(function(item){
return item < mean(array);
});
}
var a=[1,2,3,4];
alert(mean(a));
alert(belowMean(a)); //you'll get an array with those elements below the mean.
alert(belowMean(a).length); //you'll get how many elements are below the mean.
It's ugly though, I'd rather modified the array prototype to tho so.
How about loop it twice? First time for average value and second time for your count?

Resources