Mongoose aggregation pipeline: Comparing to own object - node.js

I have a sample mongoose object that looks like this:
{
_id: 5fa849ad4f6be0382363809c,
ratings: {
ratedPersonId: 7,
rating: 7,
timeSpent: 30,
timestamp: 78,
userThreshold: 5
}
},
it contains an _id and a list of ratings which is a subdocument with the following features.
I have created an aggregation pipeline like this:
const ratedUser = await this.ratingModel
.aggregate([
{ $project: { ratings: 1 } },
{ $unwind: '$ratings' },
{
$match: {
$and: [{ 'ratings.ratedPersonId': userId }, { 'ratings.rating': { $gte: 5 } }],
},
},
])
.exec()
This works for the first condition ratings.ratedPersonId: userId
My problem is that my second condition is the rating should be greater than or equal to the userThreshold field in the same object.
whenever I type that in the query it returns nothing
$and: [{ 'ratings.ratedPersonId': userId }, { 'ratings.rating': { $gte: 'ratings.threshold'} }],

Demo - https://mongoplayground.net/p/AQMsJGkoFcu
Use $expr to compare the fields
Read aggregation-expressions
$expr can build query expressions that compare fields from the same document in a $match stage.
If the $match stage is part of a $lookup stage, $expr can compare fields using let variables. See Specify Multiple Join Conditions with $lookup for an example.
$expr only uses indexes on the from the collection for equality matches in a $match stage.
$expr does not support multikey indexes.
db.collection.aggregate([
{
$project: {
ratings: 1
}
},
{
$unwind: "$ratings"
},
{
$match: {
$and: [
{
"ratings.ratedPersonId": 7
},
{
$expr: {
$gte: [
"$ratings.rating",
"$ratings.userThreshold"
]
}
}
],
},
},
])

Related

Using $elemMatch to compare object fields

I have a need to use $elemMatch in an aggregation pipeline and I need to compare 2 fields of an object from a nested array of objects:
Example collection:
name: 'xxx',
steps: [
{
userId: 'abc',
senderId: 'abc'
},
...
]
What I'm trying to do is return all that have at least 1 step where userId = senderId.
I have tried the following, but I get an error that $expr isn't allowed as a child of $elemMatch:
{
$match: {
steps: {
$elemMatch: {
$expr: { $eq: ['$userId', '$senderId'] },
},
},
},
}
Thanks.
$elemMatch can only be used in projection.
You can workaround for comparing fields in the array as below:
$set - Create new field filteredCount with get the array size $size of filtered array.
$match - Get filteredCount greater than 0.
db.collection.aggregate({
$set: {
filteredCount: {
$size: {
$filter: {
input: "$steps",
cond: {
$eq: [
"$$this.userId",
"$$this.senderId"
]
}
}
}
}
}
},
{
$match: {
"filteredCount": {
$gt: 0
}
}
})
Sample Mongo Playground

Use Mongoose aggregate to retrieve data of array inside of a schema

I have a schema in MongoDB like this.
{
productID:1,
reviews:
[
{
_id:1
likes:[{userID:1},{userID:2}],
dislikes:[{userID:3},{userID:4}],
comment:"first comment"
},
{
_id:2
likes:[{userID:1},{userID:2}],
dislikes:[{userID:3},{userID:4}],
comment:"first comment"
}
]
}
I want to fetch the likes count of a userID of a particular review for example like count of userID 2 of review id 2. I tried to get it with the help of aggregate but got stuck.
this is the code that I tried.
ProductReview.aggregate([
{ $match: { productID: productID } },
{ $match: {reviews._id:_id}}
])
but it looks like I am messing with the mongoose syntax.
To get likes by user on particular reviews then use this query
You will have to pass productID, reviewsID and userID
db.collection.aggregate([
{
$match: {
"productID": 1
}
},
{
$unwind: "$reviews"
},
{
$match: {
"reviews._id": 2
}
},
{
$unwind: "$reviews.likes"
},
{
$match: {
"reviews.likes.userID": 2
}
},
{
$group: {
_id: "$reviews.likes",
count: {
$sum: 1
}
}
},
{
$project: {
_id: 0,
userID: "$_id.userID",
count: 1
}
}
])
Mongo Playground: https://mongoplayground.net/p/wUC5tbnLC47
OLD
This returns for all reviews
Mongo Playground: https://mongoplayground.net/p/Ob5BLCAHrw1
if you want both likes and dislikes of users with one query you can use $facet
Mongo Playground: https://mongoplayground.net/p/LELfQfKjw_h

MongoDB find $in sorting issue

I need to get docs from MongoDB collection where ID's are in array:
[
'5f80a44d0179262f7c2e6a42',
'5f8c00762fae890e9c4d029c',
'5f802cf8abac1116a46bf9d4'
]
The issue is, docs are not coming in sequence of my array ID's. They are coming (1, 0, 2) for above array ID's.
How can I make them in sequence of my ID's array? I am using, NodeJs + Mongoose.
My code:
var ids = ['5f80a44d0179262f7c2e6a42','5f8c00762fae890e9c4d029c','5f802cf8abac1116a46bf9d4']
Product.find({
_id: {
$in: ids
}
})
I don't think its possible with find(), or any functionality available in MongoDB related to this,
It is possible with aggregate() but this will just fulfil your expected result,
I am not recommending to use this because this will affect performance of query response, this could be a heavy transaction.
$match your conditions
$group by null and make array of all matching objects in root
$addFields to add ids array that we search for
$unwind deconstruct ids array, this will deconstruct in sequence as per we provided in ids array
$project, $reduce input as root array and check condition if id match then return object
$replaceWith to replace root object to root
var ids = [
ObjectId("5f802cf8abac1116a46bf9d4"),
ObjectId("5f8c00762fae890e9c4d029c"),
ObjectId("5f80a44d0179262f7c2e6a42")
];
Product.aggregate([
{ $match: { _id: { $in: ids } } },
{
$group: {
_id: null,
root: { $push: "$$ROOT" }
}
},
{ $addFields: { ids: ids } },
{ $unwind: "$ids" },
{
$project: {
root: {
$reduce: {
input: "$root",
initialValue: {},
in: { $cond: [{ $eq: ["$$this._id", "$ids"] }, "$$this", "$$value"] }
}
}
}
},
{ $replaceWith: "$root" }
])
Playground

How to query nested array

Let's say I have documents like
{
name: "name1",
friends: [
{
name: "name2",
thingsWeDo: [
{
name: "running",
like_it: true
},
{
name: "swimming",
like_it: false
}
]
}
]
}
So if I want to know the name of my friend we both love swimming, how do I do it?
I know the schema design could be better, but if I want to query this schema, how do I do it?
is it possible to chain $elemMatch ?
You should $unwind friends and thingsWeDo in aggregation and then use $match to match your criteria. Check below query :
db.collectionName.aggregate({
"$unwind": "$friends" // first unwind friends array
}, {
"$unwind": "$friends.thingsWeDo" // second unwind friends.thingsWeDo
}, {
"$match": {
"friends.thingsWeDo.name": "swimming", // match your criteria
"friends.thingsWeDo.like_it": false
}
}, {
"$project": {
"name": "$friends.thingsWeDo.name",
"like_it": "$friends.thingsWeDo.like_it"
}
}).pretty()
here unwind two times for large documents query will be slow down. As per my recommendation you should change your schema structure and try to avoid unwinding
Untested, but something like this should do:
Model.find({
$and: [
{ "friends.thingsWeDo.name": 'swimming' },
{ "friends.thingsWeDo.like_it": true }
]
}, function (err, docs) {
});

How to get documents with non unique array elements?

I have the following MongoDB documents:
{
_id: ObjectId('09de14821345dda65c471c99'),
items: [
_id: ObjectId('34de64871345dfa655471c99'),
_id: ObjectId('34de64871345dfa655471c91'),
_id: ObjectId('34de64871345dfa655471c99'),
]
},
{
_id: ObjectId('09de14821345dda65c471c98'),
items: [
_id: ObjectId('24de64871345dfa61271c10'),
_id: ObjectId('24de64871345dfa61271c11'),
_id: ObjectId('24de64871345dfa61271c11'),
]
},
{
_id: ObjectId('09de14821345dda65c471c07'),
items: [
_id: ObjectId('24de64871345dfa61271c05'),
_id: ObjectId('24de64871345dfa61271c06'),
_id: ObjectId('24de64871345dfa61271c07'),
]
}
I need to find all documents with repeated items array elements. So from the documents above I want to get the following result:
db.collection.documents.find({/** need query*/}).toArray(function (err, documents) {
console.dir(documents); // documents with id's 09de14821345dda65c471c99 and 09de14821345dda65c471c98
});
How could I do that?
In order to group and match results you will need to use the Aggregation Framework or Map/Reduce rather than a simple find() query.
Example data
Your example document include some errors: a few of the ObjectIDs are too short and the array elements should either be embedded documents ({_id: ObjectId(...)}) or simple values.
For test data I've used:
db.mydocs.insert([
{
_id: ObjectId('09de14821345dda65c471c99'),
items: [
ObjectId('34de64871345dfa655471c99'),
ObjectId('34de64871345dfa655471c91'),
ObjectId('34de64871345dfa655471c99')
]
},
{
_id: ObjectId('09de14821345dda65c471c98'),
items: [
ObjectId('24de64871345ddfa61271c10'),
ObjectId('24de64871345ddfa61271c11'),
ObjectId('24de64871345ddfa61271c11')
]
},
{
_id: ObjectId('09de14821345dda65c471c07'),
items: [
ObjectId('24de64871345ddfa61271c05'),
ObjectId('24de64871345ddfa61271c06'),
ObjectId('24de64871345ddfa61271c07')
]
}])
Aggregation query
Here is an aggregation query using the mongo shell:
db.mydocs.aggregate(
// Unpack items array into stream of documents
{ $unwind: "$items" },
// Group by original document _id and item
{ $group: {
_id: { _id: "$_id", item: "$items" },
count: { $sum: 1 }
}},
// Limit to duplicated array items (1 or more count per document _id)
{ $match: {
count: { $gt: 1 }
}},
// (Optional) clean up the result formatting
{ $project: {
_id: "$_id._id",
item: "$_id.item",
count: "$count"
}}
)
Sample results
{
"_id" : ObjectId("09de14821345dda65c471c98"),
"count" : 2,
"item" : ObjectId("24de64871345ddfa61271c11")
}
{
"_id" : ObjectId("09de14821345dda65c471c99"),
"count" : 2,
"item" : ObjectId("34de64871345dfa655471c99")
}

Resources