Query on date or does not exist? - node.js

Trying to figure out if I can make this query work using mongoose and nodejs.
Product.find({
price: { $gt: 2, $lt: 3},
date: { $gt: new Date() || $exists: false}
}). exec(callback);
Does anyone know if it is possible to check if a date does not exist send it back or if the date is greater than today?
Thanks

Use $or:
Product.find({
"price": { "$gt": 2, "$lt": 3 },
"$or": [
{ "date": { "$gt": new Date() } },
{ "date": { "$exists": false } }
]
}). exec(callback);
All arguments are generally an implicit AND, so just like it is "price greater than 2 AND less than 3" you are saying in addition "AND the date is greater than this date OR date does not exist".
Just to spell out the logic in phrase form
With "multiple fields like this, THEN you actually use an $and
Product.find({
"$and": [
{ "price": { "$gt": 2, "$lt": 3 } },
{ "$or": [
{ "date1": { "$gt": new Date() } },
{ "date1": { "$exists": false } }
]},
{ "$or": [
{ "date2": { "$gt": new Date() } },
{ "date2": { "$exists": false } }
]}
]
}). exec(callback);

Related

I want a word "Distinction" those who got Greater Than=80 and "Fail" if a person got marks less than =35

My question is:
I tried following Query :
db.marks.aggregate([{$lookup:{from:"students",localField:"StudentId",foreignField:'_id',as:"students"}}, {$unwind:{path:"$Result"}},
{$project:{_id:0,"Result.Subject":1,"Result.Marks":1,"students.Name":1}},{$match:{"Result.Marks":{$gte:70}}}
])
and I am expecting to add those "Distinction" who got more than 80 marks
I don't see any reasons to use aggregation. Try this:
db.marks.updateMany({
"$expr": { "$gt": [{ "$sum": "$Result.Marks" }, 80] }
}, {
"$set": {
"Distinction": true
}
});
db.marks.updateMany({
"$expr": { "$lt": [{ "$sum": "$Result.Marks" }, 35] }
}, {
"$set": {
"Distinction": true
}
});

MongoDB Query array based on value of another field

I need to be able to find any conditions inside an array of a document in my collection based on the value of another field.
My document:
{
"totalSteps": 3,
"currentStep": 2,
"status": "submitted",
"completed": false,
"completedDate": null,
"orderBody": [
{
"status": "complete",
"stepStarted": 1617207419303,
"stepEnded": "",
"executionOutput": ""
},
{
"status": "incomplete",
"stepStarted": 1617211111113,
"stepEnded": "",
"executionOutput": ""
},
{
"status": "incomplete",
"stepStarted": 1617207419303,
"stepEnded": "",
"executionOutput": ""
}
],
}
My query:
...find($and: [
{ orderBody: {$elemMatch: { "stepStarted" : { $lte: currentTime }, status : "incomplete"}}},
{status: { $ne: "failed"}}
])
My Issue:
I need the document returned only if the value of (currentStep - 1) is the same as the matched array. Right now the query will return the document because the conditions of orderBody[2] are fulfilled. Notice the stepStarted of orderBody[2] is < orderBody[1]. currentTime is a variable passed from server in another section of code.
I've tried:
$and: [
{ currentStep:{ {$indexOfArray: {orderBody: {$elemMatch: { "stepStarted" : { $lte: currentTime }, status : "incomplete"}}} - 1}},
{status: { $ne: "failed"}}
]
$and: [
{ currentStep: { $eq: {$indexOfArray: {orderBody: {$elemMatch: { "stepStarted" : { $lte: currentTime }, status : "incomplete"}}}},
{status: { $ne: "failed"}}
]},
{ $and: [
{orderBody[currentStep - 1]: {$elemMatch: { "stepStarted" : { $lte: currentTime }, status : "incomplete"}}},
{status: { $ne: "failed"}}
]},
Any assistance on this would be greatly appreciated.
Demo - https://mongoplayground.net/p/d2ew5peV-z-
Use $project to extract exact array element pipeline you want from orderBody. Using $arrayElemAt.
$subtract currentStep value 1 to get the correct index ($toInt)
After that run your $match query on the document.
db.collection.aggregate({
$project: {
orderBody: {
"$arrayElemAt": [ "$orderBody", { $subtract: [ { $toInt: "$currentStep" }, 1 ] } ]
}
}
},
{
$match: {
"orderBody.stepStarted": { $gte: NumberLong(1217207419302) },
"orderBody.status": "incomplete"
}
})
Note- add details you want to project in $project pipeline.
Update
Demo - https://mongoplayground.net/p/E8Wo_YfFltq
Use $addFields
db.collection.aggregate({
$addFields: {
currentOrderBody: { $arrayElemAt: [ "$orderBody", { $subtract: [ { $toInt: "$currentStep" }, 1 ] } ] }
}
},
{
$match: {
"currentOrderBody.stepStarted": { $gte: NumberLong(1217207419302) },
"currentOrderBody.status": "incomplete"
}
})

Mongo aggregate - Split interval value into months affected

I have a tricky aggregation of data in mongo and I have no idea how to achieve it directly in mongo without no later data processing.
Here is an simplified example of documents in my collection
[
{
"from" : ISODate("2017-01-15T00:00:00.000Z"),
"to" : ISODate("2017-02-15T00:00:00.000Z"),
"value" : 1000
},
{
"from" : ISODate("2017-02-01T00:00:00.000Z"),
"to" : ISODate("2017-02-28T00:00:00.000Z"),
"value" : 2000
},
{
"from" : ISODate("2017-02-20T00:00:00.000Z"),
"to" : ISODate("2017-03-14T00:00:00.000Z"),
"value" : 1000
}
]
No I would like to get monthly sum of values belonging to a specific month.
[
{janurary: 500}, /* 1/2 of interval id 1 is January so take half the value */
{february: 2833}, /* 500 + 2000 + 333 */
{march: 666}, /* 2/3 of interval id 3 is March */
]
Calculation has to be precise so I can't simplify things by saying all months have exactly 30 days. But what I can do is provide this information from code for each month of the interval. So it should be possible to provide this query information january2017 = 31 days, february2017 = 28 days, march2017 = 31 days
I know I could do this in my node.js code but there might be A LOT of documents in that DB so I would rather not fetch all of these to server to perform the calculation.
Pah, I hope somebody else comes up with a nicer answer but here is one way of getting there:
db.collection.aggregate({
$addFields: {
dayFrom: { $dayOfMonth: "$from" },
dayTo: { $dayOfMonth: "$to" },
monthFrom: { $month: "$from" },
monthTo: { $month: "$to" },
numberOfDays: { $subtract: [ { $dayOfMonth: "$to" }, { $dayOfMonth: "$from" } ] },
numberOfMonths: { $subtract: [ { $month: "$to" }, { $month: "$from" } ] },
}
}, {
$addFields: {
numberOfDaysInFromMonth: { $dayOfMonth: { $subtract: [ { $dateFromParts : { year: { $year: "$from" }, month: { $add: [ "$monthFrom", 1 ] }, day: 1 } }, 1 ] } },
}
}, {
$addFields: {
numberOfDaysAccountingForFromMonth: { $subtract: [ { $add: [ "$numberOfDaysInFromMonth", 1 ] }, "$dayFrom" ] },
numberOfDaysAccountingForToMonth: { $subtract: [ "$dayTo", 1 ] }, // assuming the "to" day does not count anymore
}
}, {
$addFields: {
totalNumberOfDays: { $add: [ "$numberOfDaysAccountingForFromMonth", "$numberOfDaysAccountingForToMonth" ] }
}
}, {
$addFields: {
percentageAccountingForFromMonth: { $divide: [ "$numberOfDaysAccountingForFromMonth", "$totalNumberOfDays" ] },
percentageAccountingForToMonth: { $divide: [ "$numberOfDaysAccountingForToMonth", "$totalNumberOfDays" ] },
}
}, {
$facet: {
"from": [{
$group: {
_id: "$monthFrom",
sum: { $sum: { $multiply: [ "$value", "$percentageAccountingForFromMonth" ] } }
}
}],
"to": [{
$group: {
_id: "$monthTo",
sum: { $sum: { $multiply: [ "$value", "$percentageAccountingForToMonth" ] } }
}
}]
}
}, {
$project: {
total: { $concatArrays: [ "$from", "$to" ] }
}
}, {
$unwind: "$total"
}, {
$group: {
_id: "$total._id",
sum: { $sum: "$total.sum" }
}
})
Some remarks:
You will need to refine that to match your precise definition of
what forms part of a date range and how to count the number of days
("is 2018-01-30 to 2018-01-31 one day or is it two days?").
You might be able to beautify that query using $let and
some nesting. I thought it would be easier to use subsequent $addFields stages to make the beast easier to follow through.
The code does not support from and to values that touch more than two months (e.g. 2018-01-01 to 2018-03-01).

MongoDB aggregation pipeline with loop

I am having this aggregation pipeline code below that I would like to run for every day of the year! Essentially calculating the minimum, maximum and average temperature ("TEMP" field) for every day of the year. At the moment I am calling this piece of code 365 times, passing the start date and the end date of a day.
Obviously this is very inefficient. Is there any way to loop this within mongo so that its faster, and return an array of 365 average values, 365 min values and 365 max values or something like that. Im using a timezone library to derive the start date and end date.
collection.aggregate([
{
$match:{$and:[
{"UID" : uid},
{"TEMP" :{$exists:true}}
{"site" : "SITE123"},
{"updatedAt": {$gte : new Date(START_DATE_ARG), $lte : new Date(END_DATE_ARG)} }
]}
},
{ "$group": {
"_id": "$UID",
"avg": { $avg: $TEMP },
"min": { $min: $TEMP },
"max": { $max: $TEMP }
}
}
], function(err, result){
if (err){
cb(1, err);
}
else{
cb(0, result);
}
});
});
The datasets look like this
....
{UID: "123", TEMP: 11, site: "SITE123", updatedAt: ISODate("2014-09-12T21:55:19.326Z")}
{UID: "123", TEMP: 10, site: "SITE123", updatedAt: ISODate("2014-09-12T21:55:20.491Z")}
....
Any ideas? Maybe we can pass all the timestamps of all the days of the year in the aggregation pipeline?
Thank you!!
Why run this for every day when you can simply make the date part of the grouping key? This is what the date aggregation operators exist for, so you can aggregate by time frames in a whole period at once without looping:
collection.aggregate([
{ "$match":{
"UID": uid,
"TEMP":{ "$exists": true }
"site" : "SITE123",
"updatedAt": {
"$gte": new Date(START_DATE_ARG),
"$lte": new Date(END_DATE_ARG)
}}
}},
{ "$group": {
"_id": {
"uid": "$UID",
"year": { "$year": "$updatedAt" },
"month": { "$month": "$updatedAt" },
"day": { "$dayOfMonth" }
},
"avg": { "$avg": "$TEMP" },
"min": { "$min": "$TEMP" },
"max": { "$max": "$TEMP" }
}}
])
Or possibly just condensing the date to a timestamp value instead. A little trick of date math with date objects:
collection.aggregate([
{ "$match":{
"UID": uid,
"TEMP":{ "$exists": true }
"site" : "SITE123",
"updatedAt": {
"$gte": new Date(START_DATE_ARG),
"$lte": new Date(END_DATE_ARG)
}}
}},
{ "$group": {
"_id": {
"uid": "$UID",
"date": {
"$subtract": [
{ "$subtract": [ "$updatedAt", new Date("1970-01-01") ] },
{ "$mod": [
{ "$subtract": [ "$updatedAt", new Date("1970-01-01") ] },
1000 * 60 * 60 * 24
]}
]
}
},
"avg": { "$avg": "$TEMP" },
"min": { "$min": "$TEMP" },
"max": { "$max": "$TEMP" }
}}
])
Of course your "date range" here is now all of the dates you require to be in the results, so the start and the end dates for all the things where you intended to loop. The grouping is done in either case to reflect "one day", but of course you could change that to any interval you want to.
Also note that your use of $and here is not necessary. Queries in MongoDB "and" conditions by default. The only time you need that operator is for multiple conditions on the same field that would otherwise not be valid JSON/BSON.

How to count distinct date items in a timestamp field in Mongoose/NodeJS?

I use Mongoose in my NodeJS, and I have a collection with documents that look like this -
var SomeSchema = new Schema({
date: {
type: Date,
default: new Date()
},
some_item: {
type: String
}
});
The date field contains date with the time component (for example, 2014-05-09 09:43:02.478Z). How do I get a count of distinct items for a given date, say 2014-05-08?
EDIT: Ideally, I would like to get a count of records for each distinct date.
What you want is usage of the aggregation framework with the aggregate() command, at least for finding one date as you ask:
SomeSchema.aggregate(
[
{ "$match": {
"date": {
"$gte": new Date("2014-05-08"), "$lt": new Date("2014-05-09")
}
}},
{ "$group": {
"_id": "$some_item",
"count": { "$sum": 1 }
}}
],
function(err,result) {
// do something with result
}
);
If you are specifically looking for "counts" over several dates then you can do something like this:
SomeSchema.aggregate(
[
{ "$match": {
"date": {
"$gte": new Date("2014-05-01"), "$lt": new Date("2014-06-01")
}
}},
{ "$group": {
"_id": {
"year": { "$year": "$date" },
"month": { "$month": "$date" },
"day": { "$dayOfMonth": "$date" }
}
"count": { "$sum": 1 }
}}
],
function(err,result) {
// do something with result
}
);
And that gives you "per day" grouping. Look for the date aggregation operators in the documentation if you wish to take this further.

Resources