How to receive xml nodes inside a time range by using xquery - date

I have an xml with something like that inside
<SFEvents>
<EventList>
<Event>
<ID>1111</ID>
<Type>Measurement</Type>
<TimeStamp>2015-09-28T09:50:27.514</TimeStamp>
<Space>Main_area</Space>
<SourceID>Thermometer_3</SourceID>
<Content>
<Measurement>
<!-- From B2MML standard (OpSegmentDataType) -->
<ID/>
<Description>Temperature of a power resistance</Description>
<Value>
<ValueString>100</ValueString>
<UnitOfMeasure>oC</UnitOfMeasure>
</Value>
</Measurement>
</Content>
</Event>
<Event>..</Event>
...
</EventList>
with many events and I currently try to receive with xquery all the Event nodes that have their Timestamp inside a time range
I use this code
all_xmls_String=session.execute("xquery for $b in doc('CIDEMdb/CIDEM.xml')
let $date_string as xs:string :=$b/SFEvents/EventList/Event/TimeStamp/data()
let $date as xs:dateTime := xs:dateTime($date_string)
where $date ge xs:dateTime('"+startdate+"') and $date le
xs:dateTime('"+enddate+"') return $b/SFEvents/EventList");
but I receive this error
Cannot cast xs:untypedAtomic+ to xs:string: ("2015-09-28T09:50:27.514", ...).
Any idea?

The problem is that you are iterating over the EventList document, which has a cardinality of 1, while selecting $b/SFEvents/EventList/Event/TimeStamp/data(), a sequence of TimeStamp values, and assigning it to a variable that expects a single value. Your query also returns an EventList, but you say you want to return Events.
There are several ways to do this, but the easiest way given your existing query it to simply iterate over the Event elements instead, select the single TimeStamp value, and then return the selected Events.
for $b in doc('CIDEMdb/CIDEM.xml')//SFEvents/EventList/Event
let $date_string as xs:string :=$b/TimeStamp/data()
let $date as xs:dateTime := xs:dateTime($date_string)
where $date ge xs:dateTime('"+startdate+"') and $date le xs:dateTime('"+enddate+"')
return $b

Related

Add properties to array

Let's say I have an array of objects:
$data = #(
[pscustomobject]#{Id1='1';Id2=51213412;ID3='00:01:00'}
[pscustomobject]#{Id1='1';Id2=51213412;ID3='00:02:00'}
[pscustomobject]#{Id1='1';Id2=51213412;ID3='00:04:00'}
[pscustomobject]#{Id1='1';Id2=51213412;ID3='00:04:00'}
[pscustomobject]#{Id1='1';Id2=51213412;ID3='00:02:00'}
[pscustomobject]#{Id1='2';Id2=11112314;ID3='00:01:00'}
[pscustomobject]#{Id1='2';Id2=11112314;ID3='00:02:00'}
[pscustomobject]#{Id1='2';Id2=11112314;ID3='00:03:00'}
[pscustomobject]#{Id1='2';Id2=11112314;ID3='00:01:00'}
[pscustomobject]#{Id1='3';Id2=12512521;ID3='00:01:00'}
[pscustomobject]#{Id1='2';Id2=11112314;ID3='00:02:00'}
[pscustomobject]#{Id1='4';Id2=42112521;ID3='00:01:00'}
[pscustomobject]#{Id1='2';Id2=11112314;ID3='00:03:00'}
[pscustomobject]#{Id1='3';Id2=12512521;ID3='00:02:00'}
[pscustomobject]#{Id1='4';Id2=42112521;ID3='00:02:00'}
[pscustomobject]#{Id1='1';Id2=51213412;ID3='00:03:00'}
[pscustomobject]#{Id1='4';Id2=42112521;ID3='00:01:00'}
[pscustomobject]#{Id1='3';Id2=12512521;ID3='00:05:00'}
[pscustomobject]#{Id1='4';Id2=42112521;ID3='00:03:00'}
[pscustomobject]#{Id1='3';Id2=12512521;ID3='00:01:00'}
[pscustomobject]#{Id1='2';Id2=11112314;ID3='00:02:00'}
[pscustomobject]#{Id1='4';Id2=42112521;ID3='00:01:00'}
[pscustomobject]#{Id1='2';Id2=11112314;ID3='00:03:00'}
[pscustomobject]#{Id1='3';Id2=12512521;ID3='00:02:00'}
[pscustomobject]#{Id1='3';Id2=12512521;ID3='00:04:00'}
[pscustomobject]#{Id1='1';Id2=51213412;ID3='00:05:00'}
[pscustomobject]#{Id1='4';Id2=42112521;ID3='00:01:00'}
[pscustomobject]#{Id1='3';Id2=12512521;ID3='00:05:00'}
[pscustomobject]#{Id1='5';Id2=53252352;ID3='00:01:00'})
I would like to convert property ID3, that is time to display only minutes so I can ultimately make a sum of them. I found a code that can convert it to minutes:
$minutes = foreach ($mins in $data.ID3) {
$TimeSpan = [System.TimeSpan]::Parse($mins)
[System.Math]::Round($TimeSpan.TotalMinutes,0)}
But now I get the results in $minutes variable. What is the fastest way to replace ID3 values with converted value?
Use a calculated property expression with Select-Object to select a new "column" based on the value of an existing property:
$data |Select-Object ID1,ID2,ID3,#{Name='Minutes';Expression={[math]::Round([timespan]::Parse($_.ID3).TotalMinutes, 0)}}
This will create a new object for every input object, each with the 3 properties from the input object and a new property Minutes with the parsed value
If you want to "overwrite" the existing ID3 property, simply omit it from the list of property names and supply it as the name for the calculated property instead:
$data |Select-Object ID1,ID2,#{Name='ID3';Expression={[math]::Round([timespan]::Parse($_.ID3).TotalMinutes, 0)}}
If you want to update the object's values, you can enumerate them and reassign them like this:
foreach($i in $data) {
$i.ID3 = ([timespan] $i.ID3).Minutes
}
However, since you mention you want the sum of them, you might be just looking for this:
[timespan]::FromMilliseconds(
[System.Linq.Enumerable]::Sum(
[int[]] ([timespan[]] $data.ID3).TotalMilliseconds
)
).TotalMinutes

Create an incrementing variable from 2 variables in PowerShell

OK, First I consider myself a newbie and have much to learn about PowerShell and this is my first post ever.
I am trying to loop through some data and put it into a custom object and put them into separate arrays for later use. The issue is that I want to create a variable representing $week_data1 by using a counter $i so I can reduce the amount of code required. I do have a concatenated variable being written out: write-host '$week++ ='$week$i But I think it is being represented as a string?
How can I get $week_data$i to represent the array to insert the data?
Input data. Each week ends on Saturday.
$week1=#('2021-05-01')
$week2=#('2021-05-02', '2021-05-03', '2021-05-04', '2021-05-05', '2021-05-06', '2021-05-07', '2021-05-08')
$week3=#('2021-05-09', '2021-05-10', '2021-05-11', '2021-05-12', '2021-05-13', '2021-05-14', '2021-05-15')
$week4=#('2021-05-16', '2021-05-17', '2021-05-18', '2021-05-19', '2021-05-20', '2021-05-21', '2021-05-22')
$week5=#('2021-05-23', '2021-05-24', '2021-05-25', '2021-05-26', '2021-05-27', '2021-05-28', '2021-05-29')
$week6=#('2021-05-30', '2021-05-31')
$month =#($week1, $week2, $week3, $week4, $week5, $week6)
Create the output structures to be populated.
$week_data1=#()
$week_data2=#()
$week_data3=#()
$week_data4=#()
$week_data5=#()
$week_data6=#()
$month_data =#($week_data1, $week_data2, $week_data3, $week_data4, $week_data5, $week_data6)
Loop through the array and count the week number that is being processed.
$i = 0
foreach($week in $month)
{ $i++
$n=0
Here I can write out a Variable and it concatenates properly.
**write-host '$week++ ='$week$i**
foreach($day in $week)
{$n++
write-host '$day ='$day
Pull in data from a .csv file to populate the custom object.
foreach($line in $csv)
{
if($line -match $day)
Match the line in the CSV file that has the correct Date in it. One line in the file per date in the month.
{ #write-host '$line.Day = ' $line.Day
# custom object to be used later
$date_data = [PSCustomObject] #{
week_numb = $i
date = $line.Day
attempts = $line.Attempts
connects = $line.Connects
}
I have tried different syntax versions but it does not work here? I want to put the custom object data into the new array for the week being processed.
#write-host '$week_data[$i]='$week_data[$i]
$week_data$i += $date_data # Add data from csv file into a
#$week_data[$i] += $date_data
}
}
}
}
Issue using $week_data$i as a variable I get an error:
At line:38 char:17
$week_data$i += $date_data # Add data from csv file into a
~~
Unexpected token '$i' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken
You're looking for variable indirection, i.e. the ability to refer to a variable indirectly, by a name stored in another variable or returned from an expression.
Note, however, that there are usually superior alternatives, such as using arrays or hashtables as multi-value containers - see this answer for an example.
If you do need to use variable indirection, use Get-Variable and Set-Variable:
$week_data1 = 'foo', 'bar'
$i = 1
# Same as: $week_data1
# Note that "$" must NOT be specified as part of the name.
Get-Variable "week_data$i" -ValueOnly
# Same as: $week_data1 = 'baz', 'quux'
Set-Variable "week_data$i" baz, quux
# Updating an existing value requires nesting the two calls:
# Same as: $week_data1 += 'quuz'
Set-Variable "week_data$i" ((Get-Variable "week_data$i" -ValueOnly) + 'quuz')
As an aside: "extending" an array with += is convenient, but inefficient: a new array must be created behind the scenes every time - see this answer.
Similarly, calling cmdlets to set and get variables performs poorly compared to direct assignments and variable references.
See this answer for applying the indirection technique analogously to environment variables, using Get-Content / Set-Content and the Env: drive.
As for what you tried:
$week_data$i = ... is an assignment expression, which is interpreted as directly juxtaposing two variables, $week_data and $i, which causes the syntax error you saw.
By contrast, something like Write-Output $week_data$i is a command, and while $week_data$i is also interpreted as two variable references, as a command argument it is syntactically valid, and would simply pass the (stringified) concatenation of the two variable values; in other words: $week_data$i acts as if it were double-quoted, i.e. an expandable string, and the command is therefore equivalent to Write-Output "$week_data$i"
Unrelated to the answer, but likely helpful for you, I have a function that will determine what week in a month a given date is.
Function Get-Week{
[cmdletbinding()]
param([parameter(ValueFromPipeline)][string[]]$Date)
process{
ForEach($Day in $Date){
$DTDay=[datetime]$Day
$Buffer = ([datetime]("{0}-01-{1}" -f $DTDay.Month,$DTDay.Year)).dayofweek.value__ -1
[math]::Truncate(($DTDay.Day+$Buffer)/7)+1
}
}
}
So you feed that a string that can be converted to a date like:
'5-13-2021' | Get-Week
or
Get-Week '5-13-2021'
and you get back a number indicating what week (ending on Saturday) of the month that day falls in.

combining objects and arrays with condition in powershell

My data($messages) looks like this
Source: sourceA
Message : {#{messagetext = abc;publishedtime = 10/5/2020}, #{messagetext = def;publishedtime = 10/12/2020}
I am trying to filter the message by displaying only messages that happened from last week until now then combining it with the source property. It is possible that a source may have multiple lines if several publishedtime happened last week.
$filterDate = (Get-Date).AddDays(-7)
$messages | select source, #{n='messagetext' ; e={???}} , #{n='publishedtime' ; e={???}}
I am lost on how to add the calculated property with the condition. I was also thinking if this could be done with a loop.
I also tried the following but having some errors
$Messages | Where-Object {([DateTime]($_.messages).publishedtime).ToUniversalTime() -ge $filterDate} but having some error
There is no need to use a calculated property. First filter the $messages collection by date. Like so,
$filterDate = (Get-Date).AddDays(-7)
$lastWeek = $messages | ? { $_.PublishedTime -ge $filterDate }
Now the $lastWeek collection contains only messages that were published within a week. Depending on the exact format of PublishedTime and your locale settings, it might need to be parsed as a date. If that's the case, please edit the question and show a few examples of actual data. Sanitize the messages if needed.
I think you should always compare dates to dates, and not compare a datetime object to a string, especially since in your question it is unclear what exact format the date is in.
Probably the format used is 'M/d/yyyy', but it could just aswell be 'd/M/yyyy' or even 'MM/d/yyyy'
$dateFormat = 'M/d/yyyy' # <-- this is unclear in the question, could also be 'd/M/yyyy' or 'MM/d/yyyy'
# you can set the Time part to all zero, as the dates in the $Messages objects also do not have that
$filterDate = (Get-Date).AddDays(-7).Date
$Messages | Where-Object {[DateTime]::ParseExact($_.Message.publishedtime, $dateFormat, $null) -ge $filterDate}

Using MarkLogic to counting

I am a beginner in the field of MarkLogic and need help in solving
this question with clarification please. This is an example of xml
classes. I need a function to count the number of classes a student
attends, use map
<Classes>
<Class>
<Class-name>math</Class-name>
<Student-name>Jon</Student-name>
<Student-name>Sam</Student-name>
</Class>
<Class>
<Class-name>Sciences</Class-name>
<Student-name>Jon</Student-name>
<Student-name>Jack</Student-name>
<Student-name>Nay</Student-name>
</Class>
<Class>
<Class-name>Languages</Class-name>
<Student-name>Jon</Student-name>
<Student-name>Sam</Student-name>
<Student-name>Nay</Student-name>
</Class>
</Classes>
A way to count without maps would be to collect a distinct list of Student-name, and then use those names to get a count of the Student-name elements with those names:
for $student in fn:distinct-values($Classes/Class/Student-name)
return
$student||":"||count($Classes/Class[Student-name=$student])
A way to achieve the same thing with maps would be to walk over each of the Student-name elements, putting an entry in the map that increments the current count by 1:
let $stats := map:new()
let $_ :=
for $student in $Classes/Class/Student-name
return map:put($stats, $student, 1 + (map:get($stats, $student), 0)[1])
return
map:keys($stats) ! ( .||":"||map:get($stats, .) )

Perl Xpath: search item before a date year

I have an xml database that contains films, for example:
<film id="5">
<title>The Avengers</title>
<date>2012-09-24</date>
<family>Comics</family>
</film>
From a Perl script I want to find film by date.
If I search films of an exacly year, for example:
my $query = "//collection/film[date = 2012]";
it works exactly and return all films of 2012 year, but if I search all film before a year, it didn't work, for example:
my $query = "//collection/film[date < 2012]";
it returns all film..
Well, as usual, there's more than one way to do it. ) Either you let XPath tool know that it should compare dates (it doesn't know from the start) with something like this:
my $query = '//collection/film[xs:date(./date) < xs:date("2012-01-01")]';
... or you just bite the bullet and just compare the 'yyyy' substrings:
my $query = '//collection/film[substring(date, 1, 4) < "2012"]';
The former is better semantically, I suppose, but requires an advanced XML parser tool which supports XPath 2.0. And the latter was successfully verified with XML::XPath.
UPDATE: I'd like to give my explanation of why your first query works. ) See, you don't compare dates there - you compare numbers, but only because of '=' operator. Quote from the doc:
When neither object to be compared is a node-set and the operator is =
or !=, then the objects are compared by converting them to a common
type as follows and then comparing them. If at least one object to be
compared is a boolean, then each object to be compared is converted to
a boolean as if by applying the boolean function. Otherwise, if at
least one object to be compared is a number, then each object to be
compared is converted to a number as if by applying the number
function.
See? Your '2012-09-24' was converted to number - and became 2012. Which, of course, is equal to 2012. )
This doesn't work with any other comparative operators, though: that's why you need to either use substring, or convert the date-string to number. I supposed the first approach would be more readable - and faster as well, perhaps. )
Use this XPath, to check the year
//collection/film[substring-before(date, '-') < '2012']
Your Perl script will be,
my $query = "//collection/film[substring-before(date, '-') < '2012']";
OR
my $query = "//collection/film[substring-before(date, '-') = '2012']";
Simply use:
//collection/film[translate(date, '-', '') < 20120101]
This removes the dashes from the date then compares it for being less than 2012-01-01 (with the dashes removed).
In the same way you can get all films with dates prior a given date (not only year):
//collection/film[translate(date, '-', '') < translate($theDate, '-', '']