Extract month from timestamp mongodb - node.js

I have a mongodb like that:
{
"_id" : ObjectId("5ece47aa6510a611b47aac5a"),
"array" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac6e"),
"timestamp" : "1470420945250",
},
{
"_id" : ObjectId("5ece47aa6510a611b47a8895"),
"timestamp" : "1470420945250"
},
{..},
{..}
]
I am trying to make a query to count how many months from timestamp are January for example. Any suggestion?

mongoplayground
mongoplayground2 => Contains the count, just change the $match around
db.collection.aggregate([
{
$unwind: "$array"
},
{
$addFields: {
timestamp: {
$toDate: "$array.timestamp"
}
}
},
{
$project: {
month: {
$month: {
date: "$timestamp"
}
}
}
}
])
You may add a timezone field inside $month.

Related

Get Total Count using aggregate + facet using Mongo

I want to group my data based on event date with pagination. However what i am getting is whole record totalcount instead of eventDate count. because of this UI part is not working properly. Here is my collection sample:
{
"_id" : ObjectId("5fc4d0009a25e8cfbe306381"),
"eventDate" : ISODate("2021-11-29T01:00:00.000Z"),
"team1" : {
"tName" : "Chicago Bears",
},
"team2" : {
"tName" : "Green Bay Packers",
}
}
{
"_id" : ObjectId("5fc4d0019a25e8cfbe3063ff"),
"eventDate" : ISODate("2021-11-30T01:00:00.000Z"),
"team1" : {
"tName" : "Nashville SC",
},
"team2" : {
"tName" : "Columbus Crew",
}
}
{
"_id" : ObjectId("5fc4d0019a25e8cfbe3063f4"),
"eventDate" : ISODate("2021-11-30T01:00:00.000Z"),
"team1" : {
"tName" : "yyyy",
},
"team2" : {
"tName" : "xxxx",
}
}
here is my query:
db.getCollection('game').aggregate([
{ $addFields: { "newEventDate": {$dateToString:{ format: "%Y-%m-%d", date: "$eventDate" }}}},
{ "$match": {
"eventDate": { $gte: new Date() }
}
},
{ "$facet": {
"resultData": [
{ "$match": {
"eventDate": { $gte: new Date() }
}
},
{ "$group": {
"_id": "$newEventDate",
"games": {$push: {
team1:"$team1",
team2:"$team2"
}}
}
},
{ $sort: {eventDate: 1} },
{
$limit: 1
}
],
"pageInfo": [
{ "$count": "totalRecords" }
]}
}
]);
After executing this query this is my response:
{
"resultData" : [
{
"_id" : "2021-11-29",
"games" : [
{
"awayTeam" : {
"tName" : "Chicago Bears"
},
"homeTeam" : {
"tName" : "Green Bay Packers"
}
},
]
}
],
"pageInfo" : [
{
"totalRecords" : 3 **[here i want 2 ie total event date]**
}
]
}
$match your condition
move your $group stage outside from $facet, convert your date from string inside group, add you date in group stage because we are going to sort in next stage
$sort by eventDate ascending order
$facet, first get single record using $limit, and second part get total count of the record using $count
db.collection.aggregate([
{ $match: { eventDate: { $gte: new Date() } } },
{
$group: {
_id: {
$dateToString: {
format: "%Y-%m-%d",
date: "$eventDate"
}
},
eventDate: { $first: "$eventDate" },
games: {
$push: {
team1: "$team1",
team2: "$team2"
}
}
}
},
{ $sort: { eventDate: 1 } },
{
$facet: {
resultData: [{ $limit: 1 }],
pageInfo: [{ $count: "totalRecords" }]
}
}
])
Playground

Mongo: strings converted to date but not 'querable'

I have a collection with date stored as strings YYYY-mm-DD_HH:MM:SS.UUUZ like 2020-10-20_12:15:22.123+0100
My goal is to query on strings treating those as dates.
What am I doing:
I'm unwinding some header data on multiple documents:
{
"$unwind": {
"path": "$events",
"preserveNullAndEmptyArrays": true
}
}
and also
{
"$unwind": {
"path": "$events.hi2",
"preserveNullAndEmptyArrays": true
}
}
I'm adding a new field made with the string parsed as Date
{
"$addFields": {
"events.hi2.ConnectTimets": {
"$dateFromString": {
"dateString": "$events.hi2.ConnectTime",
"format": "%Y-%m-%d_%H:%M:%S.%L%Z"
}
}
}}
then on a $match stage I try to filter all records with date newer than 1 June 2020:
{
"$match":{
"events.hi2.ConnectTimets": {
"$gt": {"$dateFromString": {
"dateString": "2020-06-01",
"format": "%Y-%m-%d"
}
}
}
}
}
my result is Fetched 0 record(s) in 0ms
even though exists (at least a single document) in the database with a date matching the filter:
{
"_id" : ObjectId("5f438dfbf1feb13c4352e9f4"),
"timestamp" : NumberLong(1598262779045),
"attribute1" : [
"common"
],
"events" : [
{
"eventType" : "ty1",
"timestamp" : NumberLong(1598262779018),
"docId" : NumberLong(282578800148736),
"hi2" : {
"Priority" : 3,
"ClientId" : "client1",
"ConnectTime" : "2020-08-24_09:52:58.993+0000",
"Direction" : 1
}
},
{
"eventType" : "ty2",
"timestamp" : NumberLong(1598262781071),
"docId" : NumberLong(282578800148736),
"hi2" : {
"ref" : "bbbb"
}
}
]
}
When I espected something like
{
"_id" : ObjectId("5f438dfbf1feb13c4352e9f4"),
"timestamp" : NumberLong(1598262779045),
"attribute1" : [
"common"
],
"events" : [
{
"eventType" : "ty1",
"timestamp" : NumberLong(1598262779018),
"docId" : NumberLong(282578800148736),
"hi2" : {
"Priority" : 3,
"ClientId" : "client1",
"ConnectTime" : "2020-08-24_09:52:58.993+0000",
"Direction" : 1
}
}
}
Note_: the add field is ok because if i fire it without the match stage outputs a field with the string parsed as date
You should never store date/time values as string, use always proper Date objects.
Then the query is much simpler:
db.logging.aggregate([
{
$addFields: {
"events.hi2.ConnectTimets": {
$dateFromString: {
dateString: "$events.hi2.ConnectTime",
format: "%Y-%m-%d_%H:%M:%S.%L%Z"
}
}
}
},
{ $match: { "events.hi2.ConnectTimets": { $gte: ISODate("2020-06-01") } } }
])
When you have to work with date/time values then I recommend moment.js. Then you query could look like these:
{ $match: { "events.hi2.ConnectTimets": { $gte: moment("2020-06-01").toDate() } } }
{ $match: { "events.hi2.ConnectTimets": { $gte: moment("2020-06-01").tz('Europe/Zurich').toDate() } } }
{ $match: { "events.hi2.ConnectTimets": { $lte: moment.tz('Europe/Zurich').endOf('day').toDate() } } }

How to group records by day using timestamp as a field in mongodb and nodejs

I would like to group records by day for certain period. I have tried so far using this code added into the aggregate function:
{
$group : {
_id : { day: { $dayOfMonth: "$timestamp" }},
count: { $sum: 1 }
}
}
And this is how a document looks like:
{
"_id" : ObjectId("ec9cddd50e08a84cd3f4cccb"),
"orgid" : "5c48500d84430a3a4b828e85",
"timestamp" : ISODate("2019-03-28T14:00:00.000Z"),
"apiid" : {
"zxczxczxczxczxc" : {
"errortotal" : 6,
"hits" : 6,
"humanidentifier" : "Feedback",
"identifier" : "663cfc345e42401c6443cfd635301f8f",
"lasttime" : ISODate("2019-03-28T14:58:07.355Z"),
"success" : 0,
"totalrequesttime" : 0.0,
"requesttime" : 0.0
}
},
"apikeys" : {
"00000000" : {
"errortotal" : 3,
"hits" : 3,
"humanidentifier" : "",
"identifier" : "00000000",
"lasttime" : ISODate("2019-03-28T14:55:10.438Z"),
"success" : 0,
"totalrequesttime" : 0.0,
"requesttime" : 0.0
},
"cae81afc" : {
"errortotal" : 3,
"hits" : 3,
"humanidentifier" : "EE5RqcXMTqcWEx8nZv3vRATLspK2",
"identifier" : "cbe81afc",
"lasttime" : ISODate("2019-03-28T14:58:07.355Z"),
"success" : 0,
"totalrequesttime" : 0.0,
"requesttime" : 0.0
}
}
Any idea how can I achieve this?
Result I get is: [ { _id: { day: null }, count: 3 } ], it seems wrong for me since I have two documents with the same date and another document with different timestamp
UPDATE:
I also have this inside aggregate fuction:
// Project things as a key/value array, along with the original doc
{
$project: {
array: {$objectToArray: '$apikeys'},
doc: '$$ROOT',
}
},
// Match the docs with a field value of 'x'
{$match: {'array.v.humanidentifier': {$in: trialCustomerUsers}}},
If I comment this part it will work fine the grouping, but the thing is I would also do some where statement in cases where I also dont know what woudl be the key, that's why I had to add this piece of code
Just accumulate the records in a new field with the $push operator
{
$group : {
_id : { day: { $dayOfMonth: "$timestamp" }},
records: { $push: "$$ROOT" }
}
}
You have $projected your all the root document in the doc field using $$ROOT. Now your aggregation should be as followed
db.collection.aggregate([
{ "$project": {
"array": { "$objectToArray": "$apikeys" },
"doc": "$$ROOT"
}},
{ "$match": { "array.v.humanidentifier": { "$in": trialCustomerUsers }}},
{ "$group" : {
"_id" : { "day": { "$dayOfMonth": "$doc.timestamp" }},
"count": { "$sum": 1 }
}}
])
Change this line
_id : { day: { $dayOfMonth: "$timestamp" }}
to
_id : { day: { $day: "$timestamp" } }
or you can do something like this
$group : {
_id : null,
day: '$timestamp',
count: { $sum: 1 }
}

GroupBy date + mongodb

I have to aggregate the result based on the month of the given document. Consider the following as my document:
{
"_id" : ObjectId("5b3314a12b05b1b247366f48"),
"email" : "abc#gmail.com",
"qwerty":[{
"id" : "5ba4ebbad1b5eaf038841302",
"status" : "inprogress",
"Date" : "2018-08-20"
},
{
"id" : "5ba4ebbad1b5eaf038841303",
"status" : "inprogress",
"Date" : "2018-08-20"
}]
Following is my query:
var query =[
{ $match: {"email":email} },
{$unwind: "$courses" },
{$group:{_id:{$substrCP: ["$qwerty.Date", 5, 2]},count:{$sum:1}}}
];
Its working properly. But i $substrCP: ["$qwerty.Date", 5, 2] is based on the date format is "2018-08-20", what if "20-08-2018"?? So its possible to change the above query to accomodate of nay type.
Also i tried with new Date("").getMonth() but its showing as "NaN", i get to know that its not possible to use inside group.
Please suggest your ideas.
You can utilize $month in combination with $dateFromString to get what you need:
db.collection.aggregate([
{
$match: {
"email": "abc#gmail.com"
}
},
{
$unwind: "$qwerty"
},
{
$group: {
_id: {
$month: {
$dateFromString: {
dateString: "$qwerty.Date"
}
}
},
count: {
$sum: 1
}
}
}
])
You can see it here with the two different date formats.
To group per the date you can do the same without the $month:
db.collection.aggregate([
{
$match: {
"email": "abc#gmail.com"
}
},
{
$unwind: "$qwerty"
},
{
$group: {
_id: {
$dateFromString: {
dateString: "$qwerty.Date"
}
},
count: {
$sum: 1
}
}
}
])
See this version here

Date operation insede and array (aggreagation)

I want to build online test application using mongoDB and nodeJS. And there is a feature which admin can view user test history (with date filter option).
How to do the query, if I want to display only user which the test results array contains date specified by admin.
The date filter will be based on day, month, year from scheduledAt.startTime, and I think I must use aggregate framework to achieve this.
Let's say I have users document like below:
{
"_id" : ObjectId("582a7b315c57b9164cac3295"),
"username" : "lalalala#gmail.com",
"displayName" : "lalala",
"testResults" : [
{
"applyAs" : [
"finance"
],
"scheduledAt" : {
"endTime" : ISODate("2016-11-15T16:00:00.000Z"),
"startTime" : ISODate("2016-11-15T01:00:00.000Z")
},
"results" : [
ObjectId("582a7b3e5c57b9164cac3299"),
ObjectId("582a7cc25c57b9164cac329d")
],
"_id" : ObjectId("582a7b3e5c57b9164cac3296")
},
{
.....
}
],
"password" : "andi",
}
testResults document:
{
"_id" : ObjectId("582a7cc25c57b9164cac329d"),
"testCategory" : "english",
"testVersion" : "EAX",
"testTakenTime" : ISODate("2016-11-15T03:10:58.623Z"),
"score" : 2,
"userAnswer" : [
{
"answer" : 1,
"problemId" : ObjectId("581ff74002bb1218f87f3fab")
},
{
"answer" : 0,
"problemId" : ObjectId("581ff78202bb1218f87f3fac")
},
{
"answer" : 0,
"problemId" : ObjectId("581ff7ca02bb1218f87f3fad")
}
],
"__v" : 0
}
What I have tried until now is like below. If I want to count total document, which part of my aggregation framework should I change. Because in query below, totalData is being summed per group not per whole returned document.
User
.aggregate([
{
$unwind: '$testResults'
},
{
$project: {
'_id': 1,
'displayName': 1,
'testResults': 1,
'dayOfTest': { $dayOfMonth: '$testResults.scheduledAt.startTime' },
'monthOfTest': { $month: '$testResults.scheduledAt.startTime' },
'yearOfTest': { $year: '$testResults.scheduledAt.startTime' }
}
},
{
$match: {
dayOfTest: date.getDate(),
monthOfTest: date.getMonth() + 1,
yearOfTest: date.getFullYear()
}
},
{
$group: {
_id: {id: '$_id', displayName: '$displayName'},
testResults: {
$push: '$testResults'
},
totalData: {
$sum: 1
}
}
},
])
.then(function(result) {
res.send(result);
})
.catch(function(err) {
console.error(err);
next(err);
});
You can try something like this. Added the project stage to keep the test results if any of result element matches on the date passed. Add this as the first step in the pipeline and you can add the grouping stage the way you want.
$map applies an equals comparison between the date passed and start date in each test result element and generates an array with true and false values. $anyElementTrue inspects this array and returns true only if there is atleast one true value in the array. Match stage to include only elements with matched value of true.
aggregate([{
"$project": {
"_id": 1,
"displayName":1,
"testResults": 1,
"matched": {
"$anyElementTrue": {
"$map": {
"input": "$testResults",
"as": "result",
"in": {
"$eq": [{ $dateToString: { format: "%Y-%m-%d", date: '$$result.scheduledAt.startTime' } }, '2016-11-15']
}
}
}
}
}
}, {
"$match": {
"matched": true
}
}])
Alternative Version:
Similar to the above version but this one combines both the project and match stage into one. The $cond with $redact accounts for match and when match is found it keeps the complete tree or else discards it.
aggregate([{
"$redact": {
"$cond": [{
"$anyElementTrue": {
"$map": {
"input": "$testResults",
"as": "result",
"in": {
"$eq": [{
$dateToString: {
format: "%Y-%m-%d",
date: '$$result.scheduledAt.startTime'
}
}, '2016-11-15']
}
}
}
},
"$$KEEP",
"$$PRUNE"
]
}
}])

Resources