Using mongose aggregation to populate from collection in mongo - node.js

Sample document
{
_id:"123",
"completed" : [
{
"Id" : ObjectId("57caae00b2c40dd21ba089be")
"subName" : "oiyt",
"Name" : "Endo",
},
{
"Id" : ObjectId("57caae00b2c40dd21ba089be"),
"subName" : "oiyt",
"Name" : "Endo",
}
]
}
How do I access the name and subname from complete where _id matches?

You can use $filter or $unwind (or both).
This example shows how to use $filter to get the document only with one matched element in the array, and then $unwind to get easier access to the matched element.
but there are many more options to get the desired result.
db.collection.aggregate([
{
$project: {
filtered_completed: {
$filter:{
input: "$completed",
as: "complete",
cond: {
$eq: [input_id, "$$complete.Id"]
}
}
}
}
},
{
$unwind: "$filtered_completed"
// because we already filtered the 'completed' array, we will get only one document.
// but you can use it as the first aggreagation pipeline stage and match the _id
},
{
$project: {
"filtered_completed.Name": 1,
"filtered_completed.subName": 1
}
}
])
read more about $filter and $unwind

Related

how to match _id and perticular array element in mongodb without using unwind

user data includes :
"_id" : 4
"username" : "smith"
likedVideos :[
"videoId" : 10
"title" : "something"
"speaker" : "something"
"description" : "something"
]
i have a collection with userId and a array of liked videos lists.liked videos(Array) includes videoId and video details. so i need to check that is the user is already liked this video or not. so how i match userId and videoId from the collection without using unwind ?
i tried :
const data = await userModel.findOne({ _id : userId,likedVideos: { $elemMatch: { videoId : videoId } } })
but it returns all the data of that user.
const alreadyLiked = await userModel.aggregate([
{
$match: {
'_id' : userId,
'likedVideos.videoId' : videoId,
},
},
]);
this also not working as expected.
I need a perfect solution to match a element inside array without using unwind (My boss said that unwind is a costly operation it will effect the app performance). can you please help me to solve this.
Both of your queries are valid.
It will return all the users that match your query. You are matching a specific user and a movie. So if a user is returned, it means that the user has already liked the video.
Nevertheless $elemMatch is useful when you have multiple conditions to apply to an object. So it's better to use your second solution with
{
"_id": userId,
"likedVideos.videoId": videoId
}
If you want to keep only a given element in likedVideos, you can use $filter in an aggregate.
For example
db.collection.aggregate([
{
$match: {
"_id": 1
}
},
{
$project: {
list: {
$filter: {
input: "$likedVideos",
as: "item",
cond: {
$eq: [
"$$item.videoId",
1
]
}
}
}
}
}
])
it will only return the movies in likedVideoswith id=1
try it here
The best way to filter elements in a subarray is by using an Aggregation with $match and $project.
Example:
[{
$match: {
_id: 'userId',
likedVideos.videoId: 'videoId'
}
}, {
$project: {
'likedVideos': {
$filter: {
input: '$likedVideos',
as: 'item',
cond:
{$eq: ["$$item.videoId","videoId"]}
}
}
}
}]

MongoDB query to get the sum of all document's array field length

Below is the sample document of a collection, say "CollectionA"
{
"_id" : ObjectId("5ec3f19225701c4f7ab11a5f"),
"workshop" : ObjectId("5ebd37a3d33055331eb4730f"),
"participant" : ObjectId("5ebd382dd33055331eb47310"),
"status" : "analyzed",
"createdBy" : ObjectId("5eb7aa24d33055331eb4728c"),
"updatedBy" : ObjectId("5eb7aa24d33055331eb4728c"),
"results" : [
{
"analyze_by" : {
"user_name" : "m",
"user_id" : "5eb7aa24d33055331eb4728c"
},
"category_list" : [
"Communication",
"Controlling",
"Leading",
"Organizing",
"Planning",
"Staffing"
],
"analyzed_date" : ISODate("2020-05-19T14:48:49.993Z"),
}
],
"summary" : [],
"isDeleted" : false,
"isActive" : true,
"updatedDate" : ISODate("2020-05-19T14:48:50.827Z"),
"createdDate" : ISODate("2020-05-19T14:47:46.374Z"),
"__v" : 0
}
I need to query all the documents to get the "results" array length and return a sum of all document's "results" length.
For example,
document 1 has "results" length - 5
document 2 has "results" length - 6
then output should be 11.
Can we write a query, instead of getting all, iterating and the adding the results length??
If I had understand clearly you would like to project the length of the result attribute.
So you should check the $size operator would work for you.
https://docs.mongodb.com/manual/reference/operator/aggregation/size/
You can use $group and $sum to calculate the total size of a field which contains the size of your results array. To create the field, You can use $size in $addFields to calculate the size of results in each document and put it the field. As below:
db.getCollection('your_collection').aggregate([
{
$addFields: {
result_length: { $size: "$results"}
}
},
{
$group: {
_id: '',
total_result_length: { $sum: '$result_length' }
}
}
])
You use an aggregation grouping query with $sum and $size aggregation operators to get the total sum of array elements size for all documents in the collection.
db.collection.aggregate( [
{
$group: {
_id: null,
total_count: { $sum: { $size: "$results" } }
}
}
] )
Aggregation using Mongoose's Model.aggregate():
SomeModel.aggregate([
{
$group: {
_id: null,
total_count: { $sum: { $size: "$results" } }
}
}
]).
then(function (result) {
console.log(result);
});

how to group in mongoDB and return all fields in result

I am using aggregate method in mongoDB to group but when I use $group it returns the only field which I used to group. I have tried $project but it is not working either. I also tried $first and it worked but the result data is now in different format.
The response format I need looks like:
{
"_id" : ObjectId("5b814b2852d47e00514d6a09"),
"tags" : [],
"name" : "name here",
"rating" : "123456789"
}
and after adding $group in my query.response is like this, the value of _id changes. (and the $group is taking only _id, if i try any other keyword it throws an error of accumulator something. please explain this also.)
{
"_id" :"name here" //the value of _id changed to the name field which i used in $group condition
}
I have to remove the duplicates in name field, without changing any structure and fields. also I am using nodeJS with mongoose, so please provide the solution that works with it.
You can use below aggregation query.
$$ROOT to keep the whole document per each name followed by $replaceRoot to promote the document to the top.
db.col.aggregate([
{"$group":{"_id":"$name","doc":{"$first":"$$ROOT"}}},
{"$replaceRoot":{"newRoot":"$doc"}}
])
user2683814's solution worked for me but in my case, I have a counter accumulator when we replace the newRoot object, the count field is missing in the final stage so I've used $mergeObjects operator to get my count field back.
db.collection.aggregate([
{
$group: {
_id: '$product',
detail: { $first: '$$ROOT' },
count: {
$sum: 1,
},
},
},
{
$replaceRoot: {
newRoot: { $mergeObjects: [{ count: '$count' }, '$detail'] },
},
}])
When you group data on any database, it means you want to perform accumulated operation on the required field and the other field which will not be include in accumulated operation will be used in group like
db.collection.aggregate([{
$group: {
_id: { field1: "", field1: "" },
acc: { $sum: 1 }
}}]
here in _id object will contains all other fields which you want to hold.
for your data you can try this
db.collection.aggregate([{
$group: {
_id: "$name",
rating: { $first: "$rating" },
tags: { $first: "$tag" },
docid: { $first: "$_id" }
}
},
{
$project: {
_id: "$docid",
name: "$_id",
rating: 1,
tags: 1
}
}])
You can use this query
db.col.aggregate([
{"$group" : {"_id" : "$name","data" : {"$first" : "$$ROOT"}}},
{"$project" : {
"tags" : "$data.tags",
"name" : "$data.name",
"rating" : "$data.rating",
"_id" : "$data._id"
}
}])
I wanted to group my collection by groupById field and store it as key value pairs having key as groupById and value as all the items of that group.
db.col.aggregate([{$group :{_id :"$groupById",newfieldname:{$push:"$"}}}]).pretty()
This is working fine for me..

How to use $subtract with array of document in mongoDB

We have two collections in which have users and organizations.
db.users.aggregate([
{$match: {"organization.metaInfo":{"$ne": "disabled"}}},
{"$unwind":"$organization"},
{"$group":{"_id":"$organization.organizationId", "count":{"$sum":1}}},
{$lookup: {from: "organisations",
localField: "_id",
foreignField: "_id", as: "orgDta"}},
{"$project":{"_id":1, "count":1,
"orgDta.organizationLicence.userCount":1
}}
])
When this query is performed return a result like which is good to me.
{
"_id" : "768d3090-d4f5-11e7-a503-9b68b90cdb4e",
"count" : 5.0,
"orgDta" : [{
"organizationLicence" : {
"userCount" : 50
}
}]
},
{
"_id" : "d9933740-c29c-11e7-9481-b52c5f3e2e70",
"count" : 1.0,
"orgDta" : [{
"organizationLicence" : {
"userCount" : 1
}
}]
},
{
"_id" : "5386ebc0-c29b-11e7-9481-b52c5f3e2e70",
"count" : 1.0,
"orgDta" : [{
"organizationLicence" : {
"userCount" : 1
}
}]
}
Now, I want to perform a subtract operation in between count and userCount.But I don't know that how to use here.
I was trying together with $project
{"$project":{"_id":1, "count":1, "orgDta.dObjects":1, "orgDta.organizationLicence.userCount":1,
"remainingUser": { $subtract: [ "$orgDta.organizationLicence.userCount", "$count"]}
But Mongo returns error
{
"message" : "cant $subtract adouble from a array",
"stack" : "MongoError: cant $subtract adouble from a array" }
Use $group instead wih $unwind (before) like this,
Aggregate pipeline
db.users.aggregate([
{
$unwind: '$orgDta'
}, {
$group: {
_id: '$_id',
remainingUser: {
$push: {
$subtract: ['$orgDta.organizationLicence.userCount', '$count']
}
}
}
}
])
What we are doing here is unwind the target array, subtract all the elements (in your case, the element child's element value) and then group the array back with result (of substraction) value.
Add other items you might want in your final result document, above is just a sample MongoDB aggregate query.

MongoDb - $match filter not working in subdocument

This is Collection Structure
[{
"_id" : "....",
"name" : "aaaa",
"level_max_leaves" : [
{
level : "ObjectIdString 1",
max_leaves : 4,
}
]
},
{
"_id" : "....",
"name" : "bbbb",
"level_max_leaves" : [
{
level : "ObjectIdString 2",
max_leaves : 2,
}
]
}]
I need to find the subdocument value of level_max_leaves.level filter when its matching with given input value.
And this how I tried,
For example,
var empLevelId = 'ObjectIdString 1' ;
MyModel.aggregate(
{$unwind: "$level_max_leaves"},
{$match: {"$level_max_leaves.level": empLevelId } },
{$group: { "_id": "$level_max_leaves.level",
"total": { "$sum": "$level_max_leaves.max_leaves" }}},
function (err, res) {
console.log(res);
});
But here the $match filter is not working. I can't find out exact results of ObjectIdString 1
If I filter with name field, its working fine. like this,
{$match: {"$name": "aaaa" } },
But in subdocument level its returns 0.
{$match: {"$level_max_leaves.level": "ObjectIdString 1"} },
My expected result was,
{
"_id" : "ObjectIdString 1",
"total" : 4,
}
You have typed the $match incorrectly. Fields with $ prefixes are either for the implemented operators or for "variable" references to field content. So you just type the field name:
MyModel.aggregate(
[
{ "$match": { "level_max_leaves.level": "ObjectIdString 1" } },
{ "$unwind": "$level_max_leaves" },
{ "$match": { "level_max_leaves.level": "ObjectIdString 1" } },
{ "$group": {
"_id": "$level_max_leaves.level",
"total": { "$sum": "$level_max_leaves.max_leaves" }
}}
],
function (err, res) {
console.log(res);
}
);
Which on the sample you provide produces:
{ "_id" : "ObjectIdString 1", "total" : 4 }
It is also good practice to $match first in your pipeline. That is in fact the only time an index can be used. But not only for that, as without the initial $match statement, your aggregation pipeline would perform an $unwind operation on every document in the collection, whether it met the conditions or not.
So generally what you want to do here is
Match the documents that contain the required elements in the array
Unwind the array of the matching documents
Match the required array content excluding all others

Resources