MongoDB Aggregate - Count objects of a specific matching field - mongodb

I want to know how to use aggregate() to take all of the objects of a specific field (i.e. "user") and count them.
This what I am doing:
I want to return a list of users with the sum of how many tweets that have made?
So I want output that looks like
Etc..
Also I don't want repeating users like
Etc..
which is what the above aggregate does.
So basically, how can I modify this aggregate to ensure the objects are unique?

I believe you will want to group by the user.id field instead of the user object. You can try doing that directly
$group: {_id: "$user.id", totalTweets: {$sum: 1} }
Or you might want to try projecting that field onto the document before grouping
$addFields: {userId: "$user.id"}
$group: {_id: "$userId", totalTweets: {$sum: 1} }

If you want whole inner user object in each documents after aggregation then you have to use $push operator in aggregation
and also you need to do the aggregation on unique id of users e.g: id or id_str instead of $user object as in your question.
db.tweets.aggregate([{ $group: {_id: "$user.id", totalTweets: { $sum: 1 }, user : { $push: "$user" } } }])
This will solved your problem. For details about $push operator, have a look at official documents $push

Related

Get count of a value of a subdocument inside an array with mongoose

I have Collection of documents with id and contact. Contact is an array which contains subdocuments.
I am trying to get the count of contact where isActive = Y. Also need to query the collection based on the id. The entire query can be something like
Select Count(contact.isActive=Y) where _id = '601ad0227b25254647823713'
I am using mongo and mongoose for the first time. Please edit the question if I was not able to explain it properly.
You can use an aggregation pipeline like this:
First $match to get only documents with desired _id.
Then $unwind to get different values inside array.
Match again to get the values which isActive value is Y.
And $group adding one for each document that exists (i.e. counting documents with isActive= Y). The count is stores in field total.
db.collection.aggregate([
{
"$match": {"id": 1}
},
{
"$unwind": "$contact"
},
{
"$match": {"contact.isActive": "Y"}
},
{
"$group": {
"_id": "$id",
"total": {"$sum": 1}
}
}
])
Example here

What is the actual difference between $project and $group?

I have read the docs and still not quite following it. According to it, it returns me specific documents according to my own specifications inside a collection. For grouping, it pretty much says the same thing: "Groups documents by some specified expression and outputs to the next stage a document for each distinct grouping"
So, what does this following code is actually doing? It seems redundant to me.
BillingCycle.aggregate([{
$project: {credit: {$sum: "$credits.value"}, debt: {$sum: "debts.value"}}
}, {
$group: {
_id: null,
credit: {$sum: "$credit"}, debt: {$sum: "debt"}
}
}, {
$project: {_id: 0, credit: 1, debt: 1 }
}]});
"Groups documents by some specified expression and outputs to the next stage a document for each distinct grouping"
The purpose of $group is not only to push some fields to next stage but to gather some element on the basis of input criteria passed in the _id attribute.
On the other, hand $project function will exclude/include some field(or custom field) to next stage. As per document you can see the definition "Passes along the documents with the requested fields to the next stage in the pipeline. The specified fields can be existing fields from the input documents or newly computed fields."
There is one case if we suppress the _id from $group then it will calculate accumulated values for all the input documents as a whole. Which seems to act like $project.
For the query on $project stage is redundant
BillingCycle.aggregate([ {
$group: {
_id: null,
credit: {$sum: "$credit.value"}, debt: {$sum: "debt.value"}
}
}, {
$project: {_id: 0, credit: 1, debt: 1 }
}]});

Getting the count of documents within a document in mongodb

I have a structure of...
{ _id = object_id,
user: name,
days: { "4/1/2010": {"checked": true},
"4/2/2011": {"checked": false)}
}
I want to get the total number of days across users. If days was an array, I would do something like...
db.collection.aggregate([{"$group": {"_id": null, {"$sum": {"$size": "$days"}}}}])
but that won't work since I can't use size. Anyone have suggestions?
Note: There may be a different number of days missing in the data structure for each user which is why I want to check the count within each user's days
You can use aggregation pipeline with $objectToArray stage to convert days pair into arrays followed by $sum and $size in a $group stage in 3.4.
db.collection.aggregate([
{"$group":{
"_id":null,
"count":{
"$sum":{
"$size":{"$objectToArray":"$days"}
}
}
}}
])

mongo find limit each match

I have a mongo collection which looks something like this:
{
title: String,
category: String
}
I want to write a query that selects various categories, similar to this:
Collection.find({category: {$in: ['Books', 'Cars', 'People']});
But I want to only select a limited number of each category, for example 5 of each book, car, people. How do I write such a query? Can I do it one query or must I use multiple ones?
You can do it using mongodb aggregation. Take a look at this pipeline:
Filter all documents by categories(using $match).
Group data by categories and create array for items with the same category(using $group and $push).
Get a subset of each array with a limited maximum length(using $project and $slice).
Try the following query:
db.collection.aggregate([
{$match: {category: {$in: ['Books', 'Cars', 'People']}}},
{$group: {_id: "$category", titles: {$push: "$title"}}},
{$project: {titles: {$slice: ["$titles", 5]}}}
])

Meteor + Mongo (2.6.7) Pushing Document to Array in Sorted Order

I have a document with an array (which should be denormalised, but can't be because the reactive events will fire "add" too many times at client startup).
I need to be able to push a document to that array, and keep it in sorted (or roughly sorted) order. I've tried this query:
{ $push: {
'events': {
$each: [{'id': new Mongo.ObjectID, 'start':startDate,...}],
$sort: {'start': 1},
$slice: -1
}
}
But it requires the $slice operator to be present... I don't want to delete all my old data, I just want to be able to insert data into an array, and then have that array be sorted so that I can query the array later and say "slice greater than or equal to time X".
Is this possible?
Edit:
This mongo aggregate query nearly works, except for one level of document in the result array, but aggregating is not reactive (probably because they're expensive computations). Here is the aggregate query if anyone can see how to translate it to a find, or why it can't be translated:
Coll.aggregate({$unwind: '$events'},
{$sort: {'events.start':1}},
{$match: {'events.start': {$gte: new Date()}}},
{$group: {_id: '$_id', 'events': {$push: '$events'} }})