mongodb aggregation get the total number of matched document - node.js

I have a following sample docs saved in mongogb, like:
{
name: 'andy',
age: 19,
description: 'aaa aaa aaa'
}
{
name: 'andy',
age: 17,
description: 'bbb bbb bbb'
}
{
name: 'leo',
age: 10,
description: 'aaa aaa aaa'
}
{
name: 'andy',
age: 17,
description: 'ccc ccc ccc'
}
what the pipeline should look like to get the total number of name in each of matched sets? so I can use this sum number for next pipe. the pipeline I currently have is this:
var pip = [
{
$match: { name: 'andy' }
}
]
and I want to get this result like
{
name: 'andy',
age: 19,
description: 'aaa aaa aaa',
total_andy: 3
}
{
name: 'andy',
age: 17,
description: 'bbb bbb bbb',
total_andy: 3
}
{
name: 'andy',
age: 17,
description: 'ccc ccc ccc',
total_andy: 3
}

I am not exactly clear as to what you want. And i don't have enough reputation to ask for that in a comment. So let me have a shot at answering. If the answer isn't what you want, clarify the question further and we'll get to it...
var term1group = {$group :
{'_id' : '$name'},
'total_names' : {$sum : 1},
'ageAndDescription' : {$addToSet : {'$age', '$description'}}
}
var term2unwind = {$unwind : '$ageAndDescription'}
var term3project = {$project : {
_id : 0,
'name' : '_id',
'age' : '$ageAndDescription.age',
'description' : '$ageAndDescription.description',
'total_name' : 1
}
db.collection.aggregate(term1group, term2unwind, term3project);
Haven't tested but i am hopeful this will work.

You just need to use a $group and $sum to do a simple count. The output won't match exactly, but you could reformat it with NodeJS easily.
You apparently want to group on the three fields shown (name, age, and description). To do that, just add the fields and a field reference (using $):
{ $match: { name: 'andy' } },
{ $group: {
_id: { name: "$name", age: "$age", description: "$description"},
count: { $sum: 1}
}
}
To add the count of each group, include a $sum of 1 (for each document that matches the group).
Your output will look something like:
{ "_id" : { "name" : "andy", "age" : 17, "description" : "ccc ccc ccc" }, "count" : 1 }
{ "_id" : { "name" : "andy", "age" : 17, "description" : "bbb bbb bbb" }, "count" : 1 }
{ "_id" : { "name" : "andy", "age" : 19, "description" : "aaa aaa aaa" }, "count" : 3 }
If you used a projection with $project, you could also format the output to more closely match your original request:
{ $match: {name: 'andy' }},
{ $group: { _id: { name: "$name", age: "$age", description: "$description"} ,
count: {$sum: 1}}
},
{ $project : { name: "$_id.name", _id: 0, age: "$_id.age",
description: "$_id.description", total_andy: "$count"
}
}
Results:
{ "name" : "andy", "age" : 17, "description" : "ccc ccc ccc", "total_andy" : 1 }
{ "name" : "andy", "age" : 17, "description" : "bbb bbb bbb", "total_andy" : 1 }
{ "name" : "andy", "age" : 19, "description" : "aaa aaa aaa", "total_andy" : 3 }

Related

How to update multiple mongodb documents with different values based on a key?

I am trying to figure out a way to update multiple documents in a collection with different values based on a key.
persons collection:
{
_id: 1,
name: "Jackie Chan",
Country: Australia
},
{
_id: 2,
name: "Brad Pitt",
Country: Russia
},
{
_id: 3,
name: "Al Pacino",
Country: USA
}
Payload:
{
_id: 1,
name:"Jackie Chan",
Country:"China"
}
,{
_id: 2,
name:"Brad Pitt",
Country:"USA"
}
persons collection after update:
{
_id: 1,
name: "Jackie Chan",
Country: "China"
},
{
_id: 2,
name: "Brad Pitt",
Country: "USA"
},
{
_id: 3,
name: "Al Pacino",
Country: "USA"
}
SQL equivalent would be :
update t1.country = t2.country from persons t1 inner join #temptable t2 on t1._id=t2._id
None of the examples mentioned here explain how to do it. Unless I am missing something?
It seems like bulk write is exactly the right tool. Simply map the payload array so to make it an array of updates, such as:
db.persons.bulkWrite(payload.map( function(p) {
return { updateOne:{
filter: {_id: p._id},
update: {$set: {Country: p.Country}}
}}
}))
//code when run from mongodb client
> db.persons.find();
{ "_id" : 1, "name" : "Jackie Chan", "Country" : "China1" }
{ "_id" : 2, "name" : "Brad Pitt", "Country" : "Russia1" }
{ "_id" : 3, "name" : "Al Pacino", "Country" : "USA1" }
> var payload=[{_id: 1,name:"Jackie Chan",Country:"China2"},
... {_id: 2,name: "Brad Pitt",Country: "Russia2"},
... {_id: 3, name: "Al Pacino",Country: "USA2"}];
> print("payload: ",payload);
payload: [object Object],[object Object],[object Object]
>
> db.persons.bulkWrite(payload.map( function(p) {
... return { updateOne:{
... filter: {_id: p._id},
... update: {$set: {Country: p.Country}}
... }}
... }));
{
"acknowledged" : true,
"deletedCount" : 0,
"insertedCount" : 0,
"matchedCount" : 3,
"upsertedCount" : 0,
"insertedIds" : {
},
"upsertedIds" : {
}
}
> db.persons.find();
{ "_id" : 1, "name" : "Jackie Chan", "Country" : "China2" }
{ "_id" : 2, "name" : "Brad Pitt", "Country" : "Russia2" }
{ "_id" : 3, "name" : "Al Pacino", "Country" : "USA2" }
>

How to add parent name with their name using parent Id

I have following array which is saved in Database. i want to modify it to display like following which show their hierarchic with parent in localeName key.
var allLocales = [
{
id: 123,
localeName: 'Test',
parentId: null
},
{
id: 456,
localeName: 'Test 1',
parentId: 123
},
{
id: 789,
localeName: 'Test 2',
parentId: 456
}
]
I want to change above array to following array by changing their display name like this using their parents.:
allLocales = [
{
id: 123,
localeName: 'Test',
parentId: null
},
{
id: 456,
localeName: 'Test > Test 1',
parentId: 123
},
{
id: 789,
localeName: 'Test > Test 1 > Test 2',
parentId: 456
}
]
Try this aggregation if you are using mongo 3.4+
you can use $graphLookup for hierarchical queries $graphLookup
db.locales.aggregate(
[
{$graphLookup : {
from : "locales",
startWith : "$parentId",
connectFromField : "parentId",
connectToField : "id",
as : "parents"
}
},
{$addFields : {localeName : {$substr : [{$concat : [{$reduce : {input : "$parents", initialValue : "", in : {$concat : ["$$value", " > ", "$$this.localeName"]}}}, " > " ,"$localeName"] }, 3 , 1000]}}},
{$project : {parents : 0}}
]
).pretty()
collection
> db.locales.find()
{ "_id" : ObjectId("5a73dead0cfc59674782913a"), "id" : 123, "localeName" : "Test", "parentId" : null }
{ "_id" : ObjectId("5a73dead0cfc59674782913b"), "id" : 456, "localeName" : "Test 1", "parentId" : 123 }
{ "_id" : ObjectId("5a73dead0cfc59674782913c"), "id" : 789, "localeName" : "Test 2", "parentId" : 456 }
>
result
> db.locales.aggregate( [ {$graphLookup : { from : "locales", startWith : "$parentId", connectFromField : "parentId", connectToField : "id", as : "parents" } }, {$addFields : {localeName : {$substr : [{$concat : [{$reduce : {input : "$parents", initialValue : "", in : {$concat : ["$$value", " > ", "$$this.localeName"]}}}, " > " ,"$localeName"] }, 3 , 100]}}}, {$project : {parents : 0}} ] ).pretty()
{
"_id" : ObjectId("5a73dead0cfc59674782913a"),
"id" : 123,
"localeName" : "Test",
"parentId" : null
}
{
"_id" : ObjectId("5a73dead0cfc59674782913b"),
"id" : 456,
"localeName" : "Test > Test 1",
"parentId" : 123
}
{
"_id" : ObjectId("5a73dead0cfc59674782913c"),
"id" : 789,
"localeName" : "Test > Test 1 > Test 2",
"parentId" : 456
}
You need to make recursive function to solve this problem.
I made like below and tested.
Please see the function.
var allLocales = [
{ id: 123, localeName: 'Test', parentId: null },
{ id: 456, localeName: 'Test 1', parentId: 123 },
{ id: 789, localeName: 'Test 2', parentId: 456 }
];
function nameRecursion(element) {
if(element.parentId == null) {
return element.localeName
}else {
var parent = allLocales.find(item => item.id === element.parentId);
return nameRecursion(parent) + " -> " + element.localeName;
}
}
var newArray = allLocales.map(a => Object.assign({}, a));
for(var i=0; i<allLocales.length; i++){
newArray[i].localeName = nameRecursion(allLocales[i]);
}
console.log(allLocales);
console.log(newArray);

MongoDB aggregate on Nested data

I have nested data as below,
{
"_id" : ObjectId("5a30ee450889c5f0ebc21116"),
"academicyear" : "2017-18",
"fid" : "be02",
"fname" : "ABC",
"fdept" : "Comp",
"degree" : "BE",
"class" : "1",
"sem" : "8",
"dept" : "Comp",
"section" : "Theory",
"subname" : "BDA",
"fbValueList" : [
{
"_id" : ObjectId("5a30eecd3e3457056c93f7af"),
"score" : 20,
"rating" : "Fair"
},
{
"_id" : ObjectId("5a30eefd3e3457056c93f7b0"),
"score" : 10,
"rating" : "Fair"
},
{
"_id" : ObjectId("5a337e53341bf419040865c4"),
"score" : 88,
"rating" : "Excellent"
},
{
"_id" : ObjectId("5a337ee2341bf419040865c7"),
"score" : 75,
"rating" : "Very Good"
},
{
"_id" : ObjectId("5a3380b583dde50ddcea350e"),
"score" : 72,
"rating" : "Very Good"
}
]
},
{
"_id" : ObjectId("5a3764f1bc19b77dd9fd9a57"),
"academicyear" : "2017-18",
"fid" : "be02",
"fname" : "ABC",
"fdept" : "Comp",
"degree" : "BE",
"class" : "1",
"sem" : "5",
"dept" : "Comp",
"section" : "Theory",
"subname" : "BDA",
"fbValueList" : [
{
"_id" : ObjectId("5a3764f1bc19b77dd9fd9a59"),
"score" : 88,
"rating" : "Excellent"
},
{
"_id" : ObjectId("5a37667aee64bce1b14747d2"),
"score" : 74,
"rating" : "Good"
},
{
"_id" : ObjectId("5a3766b3ee64bce1b14747dc"),
"score" : 74,
"rating" : "Good"
}
]
}
We are trying to perform aggregation using this,
db.fbresults.aggregate([{$match:{academicyear:"2017-18",fdept:'Comp'}},{$group:{_id: {fname: "$fname", rating:"$fbValueList.rating"},count: {"$sum":1}}}])
and we get result like,
{ "_id" : { "fname" : "ABC", "rating" : [ "Fair","Fair","Excellent","Very Good", "Very Good", "Excellent", "Good", "Good" ] }, "count" : 2 }
but we are expecting result like,
{ "_id" : { "fname" : "ABC", "rating_group" : [
{
rating: "Excellent"
count: 2
},
{
rating: "Very Good"
count: 2
},
{
rating: "Good"
count: 2
},
{
rating: "Fair"
count: 2
},
] }, "count" : 2 }
We want to get individual faculty group by their name and inside that group by their rating response and count of rating.
We have already tried this one but we did not the result.
Mongodb Aggregate Nested Group
This should get you going:
db.collection.aggregate([{
$match: {
academicyear: "2017-18",
fdept:'Comp'
}
}, {
$unwind: "$fbValueList" // flatten the fbValueList array into multiple documents
}, {
$group: {
_id: {
fname: "$fname",
rating:"$fbValueList.rating"
},
count: {
"$sum": 1 // this will give us the count per combination of fname and fbValueList.rating
}
}
}, {
$group: {
_id: "$_id.fname", // we only want one bucket per fname
rating_group: {
$push: { // we push the exact structure you were asking for
rating: "$_id.rating",
count: "$count"
}
},
count: {
$avg: "$count" // this will be the average across all entries in the fname bucket
}
}
}])
This is a long aggregation pipeline, there may be some aggregations that are un-necessary, so please check and discard whichever are irrelevant.
NOTE: This will only work with Mongo 3.4+.
You need to use $unwind and then $group and $push ratings with their counts.
matchAcademicYear = {
$match: {
academicyear:"2017-18", fdept:'Comp'
}
}
groupByNameAndRating = {
$group: {
_id: {
fname: "$fname", rating:"$fbValueList.rating"
},
count: {
"$sum":1
}
}
}
unwindRating = {
$unwind: "$_id.rating"
}
addFullRating = {
$addFields: {
"_id.full_rating": "$count"
}
}
replaceIdRoot = {
$replaceRoot: {
newRoot: "$_id"
}
}
groupByRatingAndFname = {
$group: {
_id: {
"rating": "$rating",
"fname": "$fname"
},
count: {"$sum": 1},
full_rating: {"$first": "$full_rating"}
}
}
addFullRatingAndCount = {
$addFields: {
"_id.count": "$count",
"_id.full_rating": "$full_count"
}
}
groupByFname = {
$group: {
_id: "$fname",
rating_group: { $push: {rating: "$rating", count: "$count"}},
count: { $first: "$full_rating"}
}
}
db.fbresults.aggregate([
matchAcademicYear,
groupByNameAndRating,
unwindRating,
addFullRating,
unwindRating,
replaceIdRoot,
groupByRatingAndFname,
addFullRatingAndCount,
replaceIdRoot,
groupByFname
])

Multiples group aggregate in mongodb

Lets say i have a collection of books like this :
{author:"john", category:"action", title:"foobar200"},
{author:"peter", category:"scifi" , title:"42test"},
{author:"peter", category:"novel", title:"whatever_t"},
{author:"jane", category:"novel", title:"the return"},
{author:"john", category:"action", title:"extreme test"},
{author:"peter", category:"scifi", title:"such title"},
{author:"jane", category:"action", title:"super book "}
I want to do a query similar to :
SELECT author,category, count(*) FROM books GROUP BY category, author
==> result :
john -> action -> 2
john -> novel -> 0
john -> scifi -> 0
jane -> action -> 1
etc...
the closest i've been to the solution is this :
db.books.aggregate(
{
$match: {category:"action"}
},
{
$group: { _id: '$author', result: { $sum: 1 } }
}
);
==> result
{ "_id" : "jane", "result" : 1 }
{ "_id" : "john", "result" : 2 }
{ "_id" : "peter", "result" : 0 }
But i can't understand how to perform the second "group by" with categories.
What is the best way to do this ?
Thanks
You can include multiple fields in the _id used by $group to provide multi-field grouping:
db.books.aggregate([
{$group: {
_id: {category: '$category', author: '$author'},
result: {$sum: 1}
}}
])
Result:
{
"_id" : {
"category" : "action",
"author" : "jane"
},
"result" : 1
},
{
"_id" : {
"category" : "novel",
"author" : "jane"
},
"result" : 1
},
{
"_id" : {
"category" : "novel",
"author" : "peter"
},
"result" : 1
},
{
"_id" : {
"category" : "scifi",
"author" : "peter"
},
"result" : 2
},
{
"_id" : {
"category" : "action",
"author" : "john"
},
"result" : 2
}

MongoDB (Mongoose) how to returning all document fields using $elemMatch

According with MongoDB documentation (http://docs.mongodb.org/manual/reference/operator/projection/elemMatch/):
{
_id: 1,
school: "school A",
zipcode: 63109,
students: [
{ name: "john", school: 102, age: 10 },
{ name: "jess", school: 102, age: 11 },
{ name: "jeff", school: 108, age: 15 }
]
}
{
_id: 2,
school: "school B",
zipcode: 63110,
students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
]
}
{
_id: 3,
school: "school C",
zipcode: 63109,
students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
]
}
{
_id: 4,
school: "school D",
zipcode: 63109,
students: [
{ name: "barney", school: 102, age: 7 },
]
}
launching:
schools.find({ zipcode: 63109}, {students: { $elemMatch: { school: 102
} } }, function (err, school) { ...}
The operation returns the following documents:
{ "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" :
10 } ] } { "_id" : 3 } { "_id" : 4, "students" : [ { "name" :
"barney", "school" : 102, "age" : 7 } ] }
But I need the value of school filed too...
{ "_id" : 1, "school": "School A", "students" : [ { "name" : "john", "school" : 102, "age" :
10 } ] } { "_id" : 3, "school": "School C" } { "_id" : 4, "school": "School D", "students" : [ { "name" :
"barney", "school" : 102, "age" : 7 } ] }
and I can't find a way to achieve this...
http://docs.mongodb.org/manual/reference/method/db.collection.find/
If the projection argument is specified, the matching documents
contain only the projection fields and the _id field. You can
optionally exclude the _id field.
but... I forced the Fields to Return using:
schools.find({ zipcode: 63109}, {school: 1, students: { $elemMatch: { school: 102 } } }, function (err, school) { ...}
and all seems to work properly...
According to mongodb [documentation][1] $elemMatch return first matching element from an array based on a condition. So you have to use $filter instead of $elemMatch to get all matching element.
I have written a solution. please take a look.
solution checkup link: https://mongoplayground.net/p/cu7Mf8XZHDI
db.collection.find({},
{
students: {
$filter: {
input: "$students",
as: "student",
cond: {
$or: [
{
$eq: [
"$$student.age",
8
]
},
{
$eq: [
"$$student.age",
15
]
}
]
}
}
}
})
[1]: http://docs.mongodb.org/manual/reference/operator/projection/elemMatch/

Resources