Mongoose aggregate pipeline date greater than query with bson varible - node.js

Actually, I am raising this question after trying previous questions answers. but, I don't find any clue that's why I am asking
Trying to achieve:
In mongoose aggregation, I am using pipeline aggregation to get documents from the collection by the following query
$match: {
$and: [
{
$expr: {
$eq: ["$device_id", "$$device_id"]
}
},
{
date: {
"$gte": "$billing_cycle_startdate",
"$lte": endOfDay
}
}
]
}
billing_cycle_startdate will be : 2020-12-31T00:00:00.000Z by following query
{
$addFields: {
billing_cycle_startdate: {
$concat: [
year, "-", month, "-", "$device_details.billing_cycle"
]
},
}
},
{ $set: { billing_cycle_startdate: { $toDate: "$billing_cycle_startdate" } }},
but this query returns empty results
if I run
{
date: {
"$gte": new Date("2020-12-31T00:00:00.000Z"),
"$lte": endOfDay
}
}
it returns results as expected
is there is any other way to convert 2020-12-31T00:00:00.000Z to queryable format without using new Date() function and also want to know what is the difference between 2020-12-31T00:00:00.000Z and new Date("2020-12-31T00:00:00.000Z")
There is any other way to achieve this query??
Thanks in advance..!

Finally, after a few days of suffering, I found what's wrong with my query
I just forget to use let syntax in my pipeline to specify a variable which is billing_cycle_startdate and forget to use it in the pipeline field stage
The pipeline cannot directly access the input document fields.
Instead, first, define the variables for the input document fields,
and then reference the variables in the stages in the pipeline.
for more: click here
then I changed my query as follows
{
$lookup: {
from: "name",
let: {
device_id: "$budget.device",
billing_cycle_startdate: "$billing_cycle_startdate"
},
pipeline: [
{
$match: {
$and: [
{
$expr: {
$eq: ["$device_id", "$$device_id"]
}
},
{
$and: [
{
$expr: {
$gte: ["$date", "$$billing_cycle_startdate"],
},
},
{
$expr: {
$lte: ["$date", endOfDay],
}
}
]
}
]
}
}
],
as: "dailyenergylogs"
}
},

Related

how to add or modify the date in mongo db pipeline through node js

I have an api in nodejs, which performs mongodb aggregation, in that pipeline the date values will add through request params with the help of an api, aggregation is working fine. but these date values are not getting evaluated.
my nodejs aggregate pipeline:
const result = await db.collection.aggregate(
{
"$match": {
"TaskCompletedDate": { "$gte": "new Date(`${req.query.startDate}`)", "$lt":"new Date(`${req.query.endDate}`)" }
}
},
{
"$group": {
"_id":{
"ProductTypeCode":"$ProductTypeCode"
},
"count": { "$sum": 1 }
}
},
{
"$project": {
"_id":0,
"concat":{ "$concat": [{ "$toString": "$count" }, "$_id.ProductTypeCode"] }
}
}
]).toArray();
Passing startDate and endDate through API like this:
http://localhost:3000/stored_procedure/HRC_getTaskCompletedCountPerProduct?startDate="2022-01-12T00:00:00.0Z"&endDate="2022-01-23T00:00:00.0Z"
The aggregation is working fine. But I am not able to see output when I am passing dates like mentioned above. Can anyone please help me on this
"new Date(`${req.query.startDate}`)"
is evaluated as a literal string. You cannot use JavaScript's backticks for string substitution inside double-quotes.
Try using
[
{
"$match": {
"TaskCompletedDate": { "$gte": new Date(`${req.query.startDate}`), "$lt": new Date(`${req.query.endDate}`) }
}
},
{
"$group": {
"_id":{
"ProductTypeCode":"$ProductTypeCode"
},
"count": { "$sum": 1 }
}
},
{
"$project": {
"_id":0,
"concat":{ "$concat": [{ "$toString": "$count" }, "$_id.ProductTypeCode"] }
}
}
]
If the dates are stored as strings (not actual date objects) in Mongo,
then use:
new Date(`${req.query.startDate}`).toISOString()
{
$match: {
"TaskCompletedDate": {
$gte: req.query.startDate,
$lte: req.query.endDate
}
}
},

Remove Embedded Documents in an Array in MongoDB with mongoose (updateOne - $pull) not work

I have an app with MongoDB (Mongoose) in NodeJs.
In a collection I have this type of documents, defined by weeks:
{
"_id":
{"$oid":"617f3f51f883fab2de3e7260"},
"endDate":{"$date":"2021-11-07T23:59:59.000Z"},
"startDate":{"$date":"2021-11-01T00:00:00.000Z"},
"wastes":[
{"timestamp":{"$date":"2021-11-01T01:00:58.000Z"},"duration":780},
{"timestamp":{"$date":"2021-11-01T01:00:58.000Z"},"duration":1140},
{"timestamp":{"$date":"2021-11-01T03:00:58.000Z"},"duration":540},
{"timestamp":{"$date":"2021-11-01T07:00:58.000Z"},"duration":540},
{"timestamp":{"$date":"2021-11-01T09:00:58.000Z"},"duration":960},
{"timestamp":{"$date":"2021-11-01T09:00:58.000Z"},"duration":1140},
{"timestamp":{"$date":"2021-11-01T15:00:58.000Z"},"duration":180},
{"timestamp":{"$date":"2021-11-01T15:00:58.000Z"},"duration":540}
...
]}
I have a function that finds wastes with the same timestamp, for example "2021-11-01T01:00:58.000Z", gives the longest duration for this timestamp.
I want to delete all entries with that timestamp:
{"timestamp":{"$date":"2021-11-01T01:00:58.000Z"},"duration":780},
{"timestamp":{"$date":"2021-11-01T01:00:58.000Z"},"duration":1140}
And insert only the one with the highest duration:
{"timestamp":{"$date":"2021-11-01T01:00:58.000Z"},"duration":1140}
I'm using updateOne with $pull and $push, but it doesn't work.
let query = {
startDate: new Date(startDayWeek),
};
let deleteProjection = {
$pull: {
wastes: { timestamp: new Date(timestampDeleteInsertion) },
},
};
let insertProjection = {
$push: { wastes: insertRegisterForTimestamp },
};
//Delete
await coleccion.updateOne(query, deleteProjection);
//Insertion
await coleccion.updateOne(query, insertProjection);
I have also tried with {upsert: false}, {multi: true}.
If I use the same commands in the MongoDB Compass shell, it works without problems:
//Delete
db.coleccion.updateOne({startDate: ISODate('2021-11-01T00:00:00')}, {$pull: {'wastes': {timestamp: ISODate('2021-11-01T01:00:58.000Z')}}})
//Insertion
db.coleccion.updateOne({startDate: ISODate('2021-11-01T00:00:00')}, {$push: {'wastes': {'timestamp':ISODate('2021-11-01T01:00:58.000Z'), 'duration': 1140}}})
You can achieve expected behaviour with Updates with Aggregation Pipeline
The aggregation will consists of 3 steps:
find out the max duration using $reduce; stored the result into a field
$filter the wastes array by keeping only elements not equal to the selected timestamp or the duration is not the max duration
$unset the helper field created in step 1
db.collection.update({},
[
{
$addFields: {
maxDuration: {
"$reduce": {
"input": "$wastes",
"initialValue": null,
"in": {
"$cond": {
"if": {
$and: [
{
$eq: [
"$$this.timestamp",
{
"$date": "2021-11-01T01:00:58.000Z"
}
]
},
{
$gt: [
"$$this.duration",
"$$value"
]
}
]
},
"then": "$$this.duration",
"else": "$$value"
}
}
}
}
}
},
{
$set: {
wastes: {
$filter: {
input: "$wastes",
as: "w",
cond: {
$or: [
{
$ne: [
"$$w.timestamp",
{
"$date": "2021-11-01T01:00:58.000Z"
}
]
},
{
$eq: [
"$$w.duration",
"$maxDuration"
]
}
]
}
}
}
}
},
{
"$unset": "maxDuration"
}
])
Here is the Mongo playground for your reference.
I have the same issue with the updateOne and pull command, if use the updateOne with push, it works.
In the mongo shell or in the compass, both situations (push/pull) works, but with mongoose, it finds the criteria but don't update/modify.
Result
{
"acknowledged" : true,
"matchedCount" : 1.0,
"modifiedCount" : 0.0
}

Mongodb $lookup using with multiple criteria mongodb

{
$lookup: {
from: "Comment",
let: {
p_id: "$_id",
d_id: "$data_id",
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$_id",
"$$p_id"
]
},
{
$eq: [
"$data_id",
"$$d_id"
]
}
]
}
}
}
],
as: "subComment"
}
}
https://mongoplayground.net/p/GbEgnVn3JSv
I am good at mongoplayground but tried to put there my thought
I want to fetch the comment of posts based on doc_id and post_id for mainComment query looks good to me but subcommand is not good. Please guide on this
Its simple as a post can have multiple comment need comment count base on Post.data._id which is equal to Comment.doc_id and Post._id is in Comment.post_id
Not sure what "mainComment" and "subComment" are, I believe you missed the dollar sign before them
{
$project: {
_id: 1,
main_comments_count: {
$size: "$mainComment"
},
sub_comments_count: {
$size: "$subComment"
},
}
}
Update
What you did wrong in the playground is that you used $data in the lookup.let stage. $data is a document and the field you actually want to lookup is $data._id.
sidenote: if you are looking up using just one field, you can simply use the localField and foreign in the lookup stage. Using let and pipeline is not necessary there.
db.setting.aggregate([
{
$lookup: {
from: "site",
"let": {
"pid": "$data._id" //here
},
"pipeline": [
{
"$match": {
"$expr": {
"$in": [
"$doc_id",
"$$pid"
]
}
}
}
],
"as": "subComment"
}
},
{
$addFields: {
countRecord: "$subComment"
}
}
])
i.e. this gives the same output
db.setting.aggregate([
{
$lookup: {
from: "site",
localField: "data._id",
foreignField: "doc_id",
as: "subComment"
}
},
{
$addFields: {
countRecord: "$subComment"
}
}
])

Get informations from 2 models with Mongoose/NodeJs [duplicate]

I have a collection called article_category which store all article_id belongs to the category with category_id with data format like so.
Collection 1: article_category
{
"article_id": 2015110920343902,
"all_category_id": [5,8,10]
}
Then I have other collection called article which store all my post
Collection 2: article
{
"title": "This is example rows in article collection"
"article_id": 2015110920343902,
},
{
"title": "Something change"
"article_id": 2015110920343903,
},
{
"title": "This is another rows",
"article_id": 2015110920343904,
}
Now I want to perform MongoDB query to find title with regex while category_id must equal to 8. Here is my query but is not work.
db.article.aggregate(
{
$match:
{
title:
{
$regex: /example/
}
}
},
{
$lookup:
{
from: "article_category",
pipeline: [
{ $match: { category_id: 8 } }
],
as: "article_category"
}
}
)
Above query only show the records which match by regex but not match by category_id.
Any idea?
First of all, it is all_category_id, not category_id. Secondly, you don't link articles - all documents will have exactly the same article_category array. Lastly, you probably want to filter out articles that don't have matched category. The conditional pipeline should look more like this:
db.article.aggregate([
{ $match: {
title: { $regex: /example/ }
} },
{ $lookup: {
from: "article_category",
let: {
article_id: "$article_id"
},
pipeline: [
{ $match: {
$expr: { $and: [
{ $in: [ 8, "$all_category_id" ] },
{ $eq: [ "$article_id", "$$article_id" ] }
] }
} }
],
as: "article_category"
} },
{ $match: {
$expr: { $gt: [
{ $size: "$article_category"},
0
] }
} }
] )
UPDATE:
If you don't match article_id, the $lookup will result with identical article_category array to all articles.
Let's say your article_category collection has another document:
{
"article_id": 0,
"all_category_id": [5,8,10]
}
With { $eq: [ "$article_id", "$$article_id" ] } in the pipeline the resulting article_category is
[
{
"article_id" : 2015110920343902,
"all_category_id" : [ 5, 8, 10 ]
}
]
without:
[
{
"article_id" : 2015110920343902,
"all_category_id" : [ 5, 8, 10 ]
},
{
"article_id": 0,
"all_category_id": [ 5, 8, 10 ]
}
]
If the later is what you need, it would be way simpler to make to find requests:
db.article.find({ title: { $regex: /example/ } })
and
db.article_category.find({ all_category_id: 8 })
You've couple of things incorrect here. category_id should be all_category_id. Use the join condition in $lookup and move the $match outside of $lookup stage with $unwind for optimized lookup.
Use $project with exclusion to drop the looked up field from final response.
Something like {$project:{article_category:0}}
Try
db.article.aggregate([
{"$match":{"title":{"$regex":/example/}}},
{"$lookup":{
"from":"article_category",
"localField":"article_id",
"foreignField":"article_id",
"as":"article_category"
}},
{"$unwind":"$article_category"},
{"$match":{"article_category.all_category_id":8}}
])
For uncorrelated subquery try
db.article.aggregate([
{"$match":{"title":{"$regex":/example/}}},
{"$lookup":{
"from":"article_category",
"pipeline":[{"$match":{"all_category_id":8}}],
"as":"categories"
}},
{"$match":{"categories":{"$ne":[]}}}
])

How to skip a document based on condition in mongodb aggregation

Let say I have a schema of blog post which contain many keys and one of them is author (ObjectId). Now I have an another collection of Block users which contains two keys: userid (ObjectId) and userWhoHasBeenBlocked (ObjectId).
Now in aggregation I want to skip those collection which has a author equals to the userWhoHasBeenBlocked.
My Approch: First level I have a match query which chcecks the country from which the post has been made. Let say there is a key of a country.
After this I have a $lookup query for block user collection as
{ $match: { country: "usa" } },
{
$lookup:
{
from: "ublocks",
let: { whoHasBeenBlocked: "$author", currentUser: userid },
pipeline: [
{
$match:
{
$expr:
{
$and:
[
{ $eq: ["$blockedUser", "$$whoHasBeenBlocked"] },
]
}
}
},
],
as: "isBlocked"
},
}
},
{ $match: { "$author": { $ne: "$isBlocked.userId" } } }
}
after this I have $projection block. this is not working. How to skip a document within aggregation. I also have pagination after this.

Resources