MongoDB Aggregate on Nested Object - node.js

Any idea why this isn't returing the average (returns null)?
Pattern.aggregate([
{ $match: {
name: 'asdfaddf'
}},
{ $unwind: "$ratings" },
{ $group : { _id: "test", ratings : { $avg : "$ratings.rating" } } }
My document looksl ike this
"ratings" : [
{
"rating" : "5",
"userID" : "5a73ef842aed0f399ff4ee40",
"_id" : ObjectId("5a74893230eed03f1ebf3c5f")
},
{
"rating" : "3",
"userID" : "awefawefawfawef",
"_id" : ObjectId("5a74893230aad03f1ebf3c5e")
}
],

Looks like using non-numeric values, as state in the mongodb documentation these will be ignored:
$avg ignores non-numeric values, including missing values. If all of the operands for the average are non-numeric, $avg returns null since the average of zero values is undefined.
https://docs.mongodb.com/manual/reference/operator/aggregation/avg/#non-numeric-or-missing-values

Related

MongoDB get the array element count based on the condition

For the given structure I need to find out the count of the likes array based on the unique slug value
{
"_id" : ObjectId("4e8ae86d08101908e1000001"),
"name" : ["Name"],
"likes" : ["emp1"],
"slug": 'slugabcd'
}
{
"_id" : ObjectId("4e8ae86d08101908e1000002"),
"name" : ["Another ", "Name"],
"likes" : ["emp1","emp2","emp4"],
"slug": 'slugxyz'
}
{
"_id" : ObjectId("4e8ae86d08101908e1000002"),
"name" : ["Another ", "Name"],
"slug": 'slugpqr'
}
Here is my code but not working
db.blog.aggregate({slug:"slugxyz"},{$project:{NumberOfItemsInArray:{$size:"likes"}}}).count();
How can we achieve this?
Without using any query stage:
db.blog.aggregate({$project:{slug : "$slug", numberOfLikes:{$size:"$likes"}}})
Try the following:
const pipeline = [
{
$group: {
_id: "$slug",
likesCount: {$sum: {$size: "$likes"}}
}
]
Here we are grouping by the unique "slug" value and counting the size of "likes" array field.

Mongodb aggregation distinct, average and sum with group by between two collection

I have user collection having data like this
{
"_id" : ObjectId("5da594c15324fec81d000027"),
"password" : "******",
"activation" : "Active",
"userType" : "Author",
"email" : "something#gmail.com",
"name" : "Something",
"profilePicture" : "profile_pictures/5da594c15324fec81d0000271607094354423image.png",
"__v" : 0
}
and On the other hand userlogs has data like this
{
"_id" : ObjectId("5fcb7bb4485c34a41900002b"),
"duration" : 2.54,
"page" : 1,
"activityDetails" : "Viewed Page for seconds",
"contentType" : "article",
"activityType" : "articlePageStayTime",
"label" : 3,
"bookId" : ObjectId("5f93e2cc74153f8c1800003f"),
"ipAddress" : "::1",
"creator" : ObjectId("5da594c15324fec81d000027"),
"created" : ISODate("2020-12-05T12:23:16.867Z"),
"__v" : 0
}
What I am trying to do is equivalent to this sql query
SELECT name,count(page),sum(duration),avg(DISTINCT(label)),COUNT(DISTINCT(bookId)) FROM users JOIN userlogs ON users._id=userlogs.creator where userlogs.activityType<>"articleListeningTime" group by users._id.
I can do normal group by and sum together.But How to do avg distinct and count distinct with this? I am using mongodb version 3.2
I don't think this require $group stage, you can use $setUnion and $size, $avg operators,
$lookup with userlogs collection
$project to show required fields, and filter userlogs as per your condition
$project to get statistics from userlogs
get total logs count using $size
get total duration sum using $sum
get average of unique label using $setUnion and $avg
get count of unique bookId using $serUnion and $size
db.users.aggregate([
{
$lookup: {
from: "userlogs",
localField: "_id",
foreignField: "creator",
as: "userlogs"
}
},
{
$project: {
name: 1,
userlogs: {
$filter: {
input: "$userlogs",
as: "u",
cond: { $ne: ["$$u.activityType", "articleListeningTime"] }
}
}
}
},
{
$project: {
name: 1,
totalCount: { $size: "$userlogs" },
durationSum: { $sum: "$userlogs.duration" },
labelAvg: { $avg: { $setUnion: "$userlogs.label" } },
bookIdCount: { $size: { $setUnion: "$userlogs.bookId" } }
}
}
])
Playground

i am not able to query the sub document in mongodb

i am not able to query results in this query,
i want result based on detail.type (like fetch record where detail.type="one") and fetch only first 10 records in detail.numbers array
{
"_id" : ObjectId("5a27b609e101b6092b4ebf91"),
"city" : "Mumbai",
"detail" : [
{
"type" : "One",
"name" : "Some name",
"_id" : ObjectId("5a27b609e101b6092b4ebf92"),
"numbers" : [
"72598xxx78",
"81301xxx88",
"83302xxx30",
"84309xxx43",
"85309xxx77",
"86309xxx61",
"87270xxx88",
"85272xxx36",
"88272xxx23",
"85276xxx01"
]
},
{
"name" : "Some name",
"type" : "two",
"_id" : ObjectId("5a28e954d4f5a30527d92a32"),
"contact" : [
"72598xxx78",
"81301xxx88",
"83302xxx30",
"84309xxx43",
"85309xxx77",
"86309xxx61",
"87270xxx88",
"85272xxx36",
"88272xxx23",
"85276xxx01"
]
},
]
}
MongoDB facilitates querying over array elements using $elemMatch operator.
According to description as mentioned into above question as a solution to it please try executing following MongoDB query to fetch required data from MongoDB collection.
db.collection.find({
detail: {
$elemMatch: {
type: 'One'
}
}
}, {
_id: 1,
city: 1,
'detail.$': 1
})
db.collection.aggregate([
{
$project:{
detail:{
$map:{
input:{$filter:{input:"$detail",as:"d",cond:{$eq:["$$d.type","One"]}}},
as:"d",
in:{
"type" : "$$d.type",
"name" : "$$d.name",
"numbers":{$slice:["$$d.numbers",10]}
}
}
}
}
}
])

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

Get Sum of Values in MongoDB

I have an array and each list item in that array is an object the has a value key. What I would like to do is add all of the values to get a total. I want to do this on the back end rather than the front end. I have tried the aggregate method, but haven't had any luck as it returns an empty array. Here is my array:
"budgets" : [
{
"name" : "check",
"value" : "1000",
"expense" : "false",
"uniqueId" : 0.9880268634296954
},
{
"name" : "car",
"value" : "500",
"expense" : "true",
"uniqueId" : 0.1904486275743693
},
{
"name" : "car2",
"value" : "500",
"expense" : "false",
"uniqueId" : 0.23043920518830419
},
{
"name" : "car23",
"value" : "500",
"expense" : "false",
"uniqueId" : 0.014449386158958077
},
{
"name" : "car235",
"value" : "500",
"expense" : "false",
"uniqueId" : 0.831609656335786
}
],
What I want to do is get the total of the values that have "expense" : "true" and the get a separate total of "expense" : "false" how would I do this? Like I said I have tried the aggregate method, but I must be doing it wrong.
If you are new to using the aggregation framework commands you might not yet be aware of the $cond operator. You can use this in order to get your totals separated:
db.collection.aggregate([
// Unwind the array to de-normalize the items
{ "$unwind": "$budgets" },
// Group the totals with conditions
{ "$group": {
"_id": "$_id",
"expense": { "$sum": {
"$cond": [
"$budgets.expense",
"$budgets.value",
0
]
}},
"nonExpense": { "$sum": {
"$cond": [
"$budgets.expense",
0,
"$budgets.value"
]
}}
}}
])
So what that will do is evaluate the true/false condition of expense as the first argument, and when the condition is actually true then the second argument of the $cond will be chosen and passed on to the $sum. If the condition evaluates to false then the second argument is chosen.
But looking at your data you appear to have a problem:
{
"name" : "check",
"value" : "1000",
"expense" : "false",
"uniqueId" : 0.9880268634296954
},
Noting here that all of the fields, and most notably the expense and value items, are all strings. So this is a problem as while we could get around evaluating the true/false values by doing string comparisons rather than direct boolean, you simply cannot co-erce a string as a number that can be passed to $sum.
So firstly you need to fix your data, unless it is not actually in the form as you have represented. But when it meets a correct form, then you can do your aggregation.
First of all covert you datatype of value from string to integer(summable) type and then use
db.collectioName.aggregate(
{$unwind:"$budgets"},
{
$group:{_id: "$budgets.expense",
total: { $sum: "$budgets.value"}}
})
and then you should get result like this
{
"result" : [
{
"_id" : "true",
"total" : 500
},
{
"_id" : "false",
"total" : 2000
}
],
"ok" : 1
}
Try something like this
db.collection.aggregate([ { "$unwind": "$budgets" },
{ $match: { expense: "true" } },
{ $group: { _id: "$_id",
name: { $addToSet: "$name" },
uniqueId: { $addToSet: "$uniqueId" },
total: { $sum: "$$budgets.value" } } }
])

Resources