Mongoose Aggregate match if an Array contains any value of another Array - node.js

I am trying to match by comparing the values inside the values of another array, and return it if any one of the values in the 1st array match any one value of the 2nd array. I have a user profile like this
{
"user": "bob"
"hobbies": [jogging]
},
{
"user": "bill"
"hobbies": [reading, drawing]
},
{
"user": "thomas"
"hobbies": [reading, cooking]
}
{
"user": "susan"
"hobbies": [coding, piano]
}
My mongoose search query as an example is this array [coding, reading] (but could include any hobby value) and i would like to have this as an output:
{
"user": "bill"
"hobbies": [reading, drawing]
},
{
"user": "thomas"
"hobbies": [reading, cooking]
}
{
"user": "susan"
"hobbies": [coding, piano]
}
I tried:
{"$match": {"$expr": {"$in": [searchArray.toString(), "$hobbies"]}}}
but this only works aslong as the search array has only one value in it.

const searchArray = ["reading", "coding"];
const orArray = searchArray.map((seachValue) => {
return {
hobies: searchValue,
}
});
collection.aggregate([{
$match: { $or: orArray }
}])
The query:
db.collection.aggregate([
{
$match: {
"$or": [
{
hobbies: "reading"
},
{
hobbies: "coding"
}
]
}
}
])
Or you can use another way, no need to handle arr:
db.collection.aggregate([
{
$match: {
hobbies: {
$in: [
"reading",
"coding"
]
}
}
}
])
Run here

Related

Nodejs Mongoose | Fetch the array of values for a given field

From the query below
let fields = { 'local.email': 1 };
UserModel.find({ '_id': { $in: userIds } }).select(fields).setOptions({ lean: true });
Result which we get is
[
{
"_id": "54bf2d7415eaaa570c9ed5a0",
"local": {
"email": "neo#q.com"
}
},
{
"_id": "54bfb753e4c9406112267056",
"local": {
"email": "test#q.com"
}
}
]
Is is possible to modify query itself to get below result
["neo#q.com", "test#q.com"]
Thanks in advance
You could use aggregate to return a list of objects with the emails and the map them to an array of strings:
const emailObjs = await UserModel.aggregate([
{
$match: {
_id: {
$in: userIds
}
}
},
{
$project: {
"_id": 0,
"email": "$local.email"
}
}
]);
const emails = emailObjs.map(obj => obj.email)
Link to playground for the query.

How to get particular details from nested object from MongoDB

I'm saving data for a NestJs based web app in MongoDB.
My MongoDB Data looks like this
"gameId": "1a2b3c4d5e"
"rounds": [
{
"matches": [
{
"match_id": "1111abc1111",
"team1": {
"team_id": "team8",
"score": 0
},
"team2": {
"team_id": "team2",
"score": 0
}
},
{
"match_id": "2222abc2222",
"team1": {
"team_id": "team6",
"score": 0
},
"team2": {
"team_id": "team5",
"score": 0
}
},
]
}
]
Here we have gameId for each game and inside each game, there are many rounds and many matches. Each match has match_id. How can I get a particular match info and edit it based on gameId & match_id?
(N.B: I'm willing to update score based on match_id)
I've tried something like this
const matchDetails = await this.gameModel.findOne({
gameId: gameId,
rounds: { $elemMatch: { match_id: match_id } },
});
But this doesn't work and returns null. How to do this correctly?
The problem is that you're applying the elemMatch on the rounds array, but it should be on rounds.matches. Changing your query to the following will fix the problem:
const matchDetails = await this.gameModel.findOne({
gameId: gameId,
"rounds.matches": { $elemMatch: { match_id: match_id } },
});
EDIT:
To only get a specific matching element, you can use a simple aggregation with $unwind and $filter:
db.collection.aggregate([
{
"$match": {
"gameId": gameId,
"rounds.matches": { $elemMatch: { match_id: match_id } }
}
},
{
"$unwind": "$rounds"
},
{
$project: {
match: {
$filter: {
input: "$rounds.matches",
as: "match",
cond: {
$eq: [
"$$match.match_id",
match_id
]
}
}
},
_id: 0
}
}
])
Example on mongoplayground.

How to add or remove a element in double nested array?

Example of the document:
{
postId:'232323',
post:'This is my first post',
commentsOnPost:[
{
commentId:'232323_8888',
comment:'Congrats',
repliesOnPost:[
{
replyId:'232323_8888_66666',
reply:'Thanks',
likesOnReply:['user1','user5','user3'],
}
]
}
]
}
I want to add userid in likesOnReply if users do not exist in likesOnReply, similarly remove userid from likesOnReply if exist.
I have tried like this but not working properly
await collection('post').findOneAndUpdate(
{
postId: postId,
'commentsOnPost.commentId': commentId,
'commentsOnPost.repliesOnPost.replyId': replyId
},
{
$push: { 'commentsOnPost.$[].repliesOnPost.$.likes': userid },
},
);
There is no straight way to do both the operation to pull or push in a single query,
There are 2 approaches,
1) Find and update using 2 queries:
use arrayFilters to updated nested array elements
$push to insert element
$pull to remove element
var post = await collection('post').findOne({
posted: postId,
ommentsOnPost: {
$elemMatch: {
commentId: commentId,
repliesOnPost: {
$elemMatch: {
replyId: replyId
likesOnReply: userid
}
}
}
}
});
var updateOperator = "$push";
// FOUND USER ID THEN DO REMOVE OPERATION
if (post) updateOperator = "$pull";
// QUERY
await collection('post').updateOne(
{ postId: postId },
{
[updateOperator]: {
"commentsOnPost.$[c].repliesOnPost.$[r].likesOnReply": userid
}
},
{
arrayFilters: [
{ "c.commentId": commentId },
{ "r.replyId": replyId }
]
}
)
Playground
2) Update with aggregation pipeline starting from MongoDB 4.2:
$map to iterate loop of commentsOnPost array check condition if commentId match then go to next process otherwise return existing object
$mergeObjects to merge current object with updated fields
$map to iterate loop of repliesOnPost array and check condition if replyId match then go to next process otherwise return an existing object
check condition for likesOnReply has userid then do remove using $filter otherwise insert using $concatArrays
await collection('post').findOneAndUpdate(
{ postId: "232323" },
[{
$set: {
commentsOnPost: {
$map: {
input: "$commentsOnPost",
in: {
$cond: [
{ $eq: ["$$this.commentId", commentId] },
{
$mergeObjects: [
"$$this",
{
repliesOnPost: {
$map: {
input: "$$this.repliesOnPost",
in: {
$cond: [
{ $eq: ["$$this.replyId", replyId] },
{
$mergeObjects: [
"$$this",
{
likesOnReply: {
$cond: [
{ $in: [userid, "$$this.likesOnReply"] },
{
$filter: {
input: "$$this.likesOnReply",
cond: { $ne: ["$$this", userid] }
}
},
{
$concatArrays: ["$$this.likesOnReply", [userid]]
}
]
}
}
]
},
"$$this"
]
}
}
}
}
]
},
"$$this"
]
}
}
}
}
}]
)
Playgorund

Mongoose modify single field from .find() response

I'm trying to modify the result of a single field from .find() to remove unnecessary data.
The field likedBy (array) should return an empty array when it doesn't contain the userId. But, when likedBy does contain the userId, it should return the array with only that userId, instead of all userIds.
const response = await MyObject.find().lean({
"likedBy": {
"$elemMatch": {
"$eq": body.userId
}
},
});
Current response when userId = 'id-1':
{
"_id": "some id",
"...rest of the fields"
"likedBy": [
"id-1",
"id-2",
"id-3",
]
},
What I want:
{
"_id": "some id",
"...rest of the fields"
"likedBy": [
"id-1",
]
},
You could define an aggregate pipeline and use $filter to only return the matched array element:
YourModel.aggregate([
{
$match: {
"likedBy": body.userId
}
},
{
$project: {
likedBy: {
$filter: {
input: "$likedBy",
as: "likedBy",
cond: {
$eq: [
"$$likedBy",
body.userId
]
}
}
},
otherField: 1
}
}
])
Here's an example on mongoplayground.

Filter with Object child in mongo using nodejs

I have a saved a collection in my database and I want to filter it using companyId and cameras using ObjectId specific.
In the follow is the collection that a want to get.
{
"_id": ObjectID("5c3b584fa7e1b10155e6325f"),
"companyId": "5c3b5468a7e1b10155e9995b",
"name": "Place Test",
"cameras": {
"0": ObjectID("5c9149e3f054d00028cc9604"),
"1": ObjectID("5c9149e3f054d00028cc9605")
}
}
I'm trying to filter like:
const placeCollection = req.app.locals.db.collection('places')
const place = placeCollection.findOne({
companyId: req.body.companyId,
cameras: { $elemMatch: { $eq: new ObjectId(req.body.cameraId) } }
})
but not working with cameras filter, only with companyId.
Since the keys in cameras are dynamically generated you need $objectToArray operator to check if any value is equal to req.body.cameraId. You can take advantage of $anyElementTrue operator here:
db.col.aggregate([
{
$match: {
$expr: {
$and: [
{
$anyElementTrue: {
$map: {
input: { $objectToArray: "$cameras" },
in: { $eq: [ "$$this.v", new ObjectId(req.body.cameraId) ] }
}
}
},
{ $eq: [ "$companyId", req.body.companyId ] }
]
}
}
}
])
Mongo playground

Resources