Mongo: how to sort by external weight - mongodb

Following this question which #NeilLunn has gracefully answered, here is my problem in more detail.
This is the set of documents, some have user_id some don't. The user_id represent the user who created the document:
{ "user_id" : 11, "content" : "black", "date": somedate }
{ "user_id" : 6, "content" : "blue", "date": somedate }
{ "user_id" : 3, "content" : "red", "date": somedate }
{ "user_id" : 4, "content" : "black", "date": somedate }
{ "user_id" : 4, "content" : "blue", "date": somedate }
{ "user_id" : 90, "content" : "red", "date": somedate }
{ "user_id" : 7, "content" : "orange", "date": somedate }
{ "content" : "orange", "date": somedate }
{ "content" : "red", "date": somedate }
...
{ "user_id" : 4, "content" : "orange", "date": somedate }
{ "user_id" : 1, "content" : "orange", "date": somedate }
{ "content" : "red", "date": somedate }
{ "user_id" : 90, "content" : "purple", "date": somedate }
The front end is pulling pages, so each page will have 10 items and I do that with limit and skip and it is working very well.
In case we have a logged in user, I would like to display to that current logged in user documents which he may find more interesting first, based on the users he interacted with.
The list of users which the current user may find interesting is sorted by score and is located outside of mongo. So the first element is the most important user which I would like to show his documents first, and the last user on the list is the least important.
The list is a simple array which looks like this:
[4,7,90,1].
The system which created this user score is not located within mongo, but I can copy the data if that will help. I can also change the array to include a score number.
What I would like accomplish is the following:
Get the documents sorted by importance of the user_id from the list, so that documents from user_id 4 will be the first to show up, documents from user_id 7 second and so on. When where are no users left on the list I would like to show the rest of the documents. Like this:
all documents with user_d:4
all documents with user_d:7
all documents with user_d:90
all documents with user_d:1
all the rest of the documents
How should I accomplish this? Am I asking too much from mongo?

Given the array [4,7,90,1] what you want in your query is this:
db.collection.aggregate([
{ "$project": {
"user_id": 1,
"content": 1,
"date": 1,
"weight": { "$or": [
{ "$eq": ["$user_id": 4] },
{ "$eq": ["$user_id": 7] },
{ "$eq": ["$user_id": 90] },
{ "$eq": ["$user_id": 1] },
]}
}},
{ "$sort": { "weight": -1, "date": -1 } }
])
So what that does is, for every item contained in that $or condition, the user_id field is tested against the supplied value, and $eq returns 1 or 0 for true or false.
What you do in your code is for each item you have in the array you build the array condition of $or. So it's just creating a hash structure for each equals condition, passing it to an array and plugging that in as the array value for the $or condition.
I probably should have left the $cond operator out of the previous code so this part would have been clearer.
Here's some code for the Ruby Brain:
userList = [4, 7, 90, 1];
orCond = [];
userList.each do |userId|
orCond.push({ '$eq' => [ 'user_id', userId ] })
end
pipeline = [
{ '$project' => {
'user_id' => 1,
'content' => 1,
'date' => 1,
'weight' => { '$or' => orCond }
}},
{ '$sort' => { 'weight' => -1, 'date' => -1 } }
]
If you want to have individual weights and we'll assume key value pairs, then you need to nest with $cond :
db.collection.aggregate([
{ "$project": {
"user_id": 1,
"content": 1,
"date": 1,
"weight": { "$cond": [
{ "$eq": ["$user_id": 4] },
10,
{ "$cond": [
{ "$eq": ["$user_id": 7] },
9,
{ "$cond": [
{ "$eq": ["$user_id": 90] },
7,
{ "$cond": [
{ "$eq": ["$user_id": 1] },
8,
0
]}
]}
]}
]}
}},
{ "$sort": { "weight": -1, "date": -1 } }
])
Note that it's just a return value, these do not need to be in order. And you can think about the generation of that.
For generating this structure see here:
https://stackoverflow.com/a/22213246/2313887

Since mongoDB version 3.2 we can use a $filter which make this much easier to maintain in case there are more than 4 scores:
db.collection.aggregate([
{
$addFields: {
weight: [
{key: 4, score: 10}, {key: 8, score: 9}, {key: 90, score: 8}, {key: 1, score: 7}
]
}
},
{
$addFields: {
weight: {
$filter: {
input: "$weight",
as: "item",
cond: {$eq: ["$$item.key", "$user_id"]}
}
}
}
},
{
$set: {
weight: {
$cond: [{$eq: [{$size: "$weight"}, 1]}, {$arrayElemAt: ["$weight", 0]}, {score: 1}]
}
}
},
{$set: {weight: "$weight.score"}},
{$sort: {weight: -1, date: -1}}
])
See how it works on the playground example

Related

How to sort by a foreign field, the foreign field not using alphabetical/numerical order? [duplicate]

Following this question which #NeilLunn has gracefully answered, here is my problem in more detail.
This is the set of documents, some have user_id some don't. The user_id represent the user who created the document:
{ "user_id" : 11, "content" : "black", "date": somedate }
{ "user_id" : 6, "content" : "blue", "date": somedate }
{ "user_id" : 3, "content" : "red", "date": somedate }
{ "user_id" : 4, "content" : "black", "date": somedate }
{ "user_id" : 4, "content" : "blue", "date": somedate }
{ "user_id" : 90, "content" : "red", "date": somedate }
{ "user_id" : 7, "content" : "orange", "date": somedate }
{ "content" : "orange", "date": somedate }
{ "content" : "red", "date": somedate }
...
{ "user_id" : 4, "content" : "orange", "date": somedate }
{ "user_id" : 1, "content" : "orange", "date": somedate }
{ "content" : "red", "date": somedate }
{ "user_id" : 90, "content" : "purple", "date": somedate }
The front end is pulling pages, so each page will have 10 items and I do that with limit and skip and it is working very well.
In case we have a logged in user, I would like to display to that current logged in user documents which he may find more interesting first, based on the users he interacted with.
The list of users which the current user may find interesting is sorted by score and is located outside of mongo. So the first element is the most important user which I would like to show his documents first, and the last user on the list is the least important.
The list is a simple array which looks like this:
[4,7,90,1].
The system which created this user score is not located within mongo, but I can copy the data if that will help. I can also change the array to include a score number.
What I would like accomplish is the following:
Get the documents sorted by importance of the user_id from the list, so that documents from user_id 4 will be the first to show up, documents from user_id 7 second and so on. When where are no users left on the list I would like to show the rest of the documents. Like this:
all documents with user_d:4
all documents with user_d:7
all documents with user_d:90
all documents with user_d:1
all the rest of the documents
How should I accomplish this? Am I asking too much from mongo?
Given the array [4,7,90,1] what you want in your query is this:
db.collection.aggregate([
{ "$project": {
"user_id": 1,
"content": 1,
"date": 1,
"weight": { "$or": [
{ "$eq": ["$user_id": 4] },
{ "$eq": ["$user_id": 7] },
{ "$eq": ["$user_id": 90] },
{ "$eq": ["$user_id": 1] },
]}
}},
{ "$sort": { "weight": -1, "date": -1 } }
])
So what that does is, for every item contained in that $or condition, the user_id field is tested against the supplied value, and $eq returns 1 or 0 for true or false.
What you do in your code is for each item you have in the array you build the array condition of $or. So it's just creating a hash structure for each equals condition, passing it to an array and plugging that in as the array value for the $or condition.
I probably should have left the $cond operator out of the previous code so this part would have been clearer.
Here's some code for the Ruby Brain:
userList = [4, 7, 90, 1];
orCond = [];
userList.each do |userId|
orCond.push({ '$eq' => [ 'user_id', userId ] })
end
pipeline = [
{ '$project' => {
'user_id' => 1,
'content' => 1,
'date' => 1,
'weight' => { '$or' => orCond }
}},
{ '$sort' => { 'weight' => -1, 'date' => -1 } }
]
If you want to have individual weights and we'll assume key value pairs, then you need to nest with $cond :
db.collection.aggregate([
{ "$project": {
"user_id": 1,
"content": 1,
"date": 1,
"weight": { "$cond": [
{ "$eq": ["$user_id": 4] },
10,
{ "$cond": [
{ "$eq": ["$user_id": 7] },
9,
{ "$cond": [
{ "$eq": ["$user_id": 90] },
7,
{ "$cond": [
{ "$eq": ["$user_id": 1] },
8,
0
]}
]}
]}
]}
}},
{ "$sort": { "weight": -1, "date": -1 } }
])
Note that it's just a return value, these do not need to be in order. And you can think about the generation of that.
For generating this structure see here:
https://stackoverflow.com/a/22213246/2313887
Since mongoDB version 3.2 we can use a $filter which make this much easier to maintain in case there are more than 4 scores:
db.collection.aggregate([
{
$addFields: {
weight: [
{key: 4, score: 10}, {key: 8, score: 9}, {key: 90, score: 8}, {key: 1, score: 7}
]
}
},
{
$addFields: {
weight: {
$filter: {
input: "$weight",
as: "item",
cond: {$eq: ["$$item.key", "$user_id"]}
}
}
}
},
{
$set: {
weight: {
$cond: [{$eq: [{$size: "$weight"}, 1]}, {$arrayElemAt: ["$weight", 0]}, {score: 1}]
}
}
},
{$set: {weight: "$weight.score"}},
{$sort: {weight: -1, date: -1}}
])
See how it works on the playground example

Break Down Multiple Arrays to Documents with Comparison

I'd like to break down a record into new multiple records in mongo. How to do it?
Current data:
{"user_id": 123, "scores": [65, 71, 79, 80], "materials": ["A", "B", "C", "D"]}
And I want to create the data below from above one:
{"user_id": 123, "score_original": 65, "score_improvement": 6, "material": "A"},
{"user_id": 123, "score_original": 71, "score_improvement": 8, "material": "B"},
{"user_id": 123, "score_original": 79, "score_improvement": 1, "material": "C"}
The question really could do with some clarification, but if your intention is to expand each item with a comparison to each "next" array element and where the arrays are always of equal length in direct comparison, then there are a couple of approaches.
That is there is a "simple way" ( at the end ) and more complex ways, depending on your needs. So to step through them so you understand what is involved from each:
Agregate
With a modern MongoDB release you can use the aggregation framework to merge and compare the array elements and then expand into new items like so:
db.getCollection('junk').aggregate([
{ "$project": {
"user_id": 1,
"data": {
"$map": {
"input": {
"$slice": [
{ "$zip": {
"inputs": [
"$scores",
{ "$map": {
"input": {
"$reverseArray": {
"$reduce": {
"input": { "$reverseArray": "$scores" },
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
[
[
"$$this",
{ "$subtract": [
{ "$arrayElemAt": [
{ "$ifNull": [{ "$arrayElemAt": ["$$value", -1] }, [] ]},
0
]},
"$$this"
]}
]
]
]
}
}
}
},
"in": { "$arrayElemAt": ["$$this",-1] }
}},
"$materials"
]
}},
{ "$subtract": [{ "$size": "$scores" },1] }
]
},
"in": {
"score_original": { "$arrayElemAt": [ "$$this", 0 ] },
"score_improvement": { "$arrayElemAt": [ "$$this", 1 ] },
"material": { "$arrayElemAt": [ "$$this", 2 ] }
}
}
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$concatArrays": [
[{ "k": "user_id", "v": "$user_id" }],
{ "$objectToArray": "$data" }
]
}
}
}}
])
Which returns the desired result:
/* 1 */
{
"user_id" : 123.0,
"score_original" : 65.0,
"score_improvement" : 6.0,
"material" : "A"
}
/* 2 */
{
"user_id" : 123.0,
"score_original" : 71.0,
"score_improvement" : 8.0,
"material" : "B"
}
/* 3 */
{
"user_id" : 123.0,
"score_original" : 79.0,
"score_improvement" : 1.0,
"material" : "C"
}
Much of the work is done with $reduce from "reversed" array content via $reverseArray since you want to compare to the "next" item. It's generally easier to do a "last" comparison than try to work with calculated index values in the aggregation framework operations, so this is why you "reverse" it.
The basic premise for the "improvement" values is to work through the "reversed" array comparing the present value to the last in the output array and calculate the difference using $subtract. Since you need to output both the "improvement" and also need the "previous" value for comparison, which is done via $arrayElemAt along with $ifNull checks to extract the value for comparison.
These are stored in "array pairs" for output before feeding to the next operation. Naturally you $reverseArray again to maintain the original order with the new output.
Since there are now essentially "three" arrays of values, one way of "combining" these into one is $zip which would make an array of arrays for each of the elements. It's not the only way, but again it's probably a bit clearer to read than juggling index values for extraction.
Then of course you use $map to get to the final "object" form for each array entry. But not before applying $slice since the "last" array element is being discarded due to no "improvement" over it's "next" item, which does not exist. At least that's following the logic you seem to present.
The final parts are simply using $unwind to turn the array construct into separate documents, and then reshaping the final output. Here this is applied using $replaceRoot as well as the $objectToArray and $arrayToObject operators to construct a new root document without explicit naming. However this may as well just be a simple $project instead:
{ "$project": {
"user_id": 1,
"score_original": "$data.score_original",
"score_improvement": "$data.score_improvement",
"material": "$data.material"
}}
So there are different ways that can be applied both there and in the "object" construction of the array as well. It's just that the newer operators such as $objectToArray require MongoDB 3.4.4 at least. All other things can be done with MongoDB 3.4.
Aggregate Alternate
You can alternately just work with the array indexes supplied using $range where available:
db.getCollection('junk').aggregate([
{ "$project": {
"_id": 0,
"user_id": 1,
"data": {
"$map": {
"input": { "$range": [ 0, { "$subtract": [{ "$size": "$scores" }, 1] } ] },
"as": "r",
"in": {
"score_original": { "$arrayElemAt": [ "$scores", "$$r" ] },
"score_improvement": {
"$subtract": [
{ "$arrayElemAt": [ "$scores", { "$add": [ "$$r", 1 ] } ] },
{ "$arrayElemAt": [ "$scores", "$$r" ] }
]
},
"material": { "$arrayElemAt": [ "$materials", "$$r" ] }
}
}
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$concatArrays": [
[{ "k": "user_id", "v": "$user_id" }],
{ "$objectToArray": "$data" }
]
}
}
}}
])
That has the same output and also follows the basic logic as shown in the following approaches.
Map Reduce
If you don't have a MongoDB 3.4 supporting the operators used, then you can always apply mapReduce and simply calculate and emit for each array value:
db.getCollection('junk').mapReduce(
function() {
for( var i=0; i < this.scores.length-1; i++ ) {
var id = this._id.valueOf() + '_' + i;
emit(id, {
"user_id": this.user_id,
"score_original": this.scores[i],
"score_improvement": this.scores[i+1] - this.scores[i],
"material": this.materials[i]
});
}
},
function() {},
{ "out": { "inline": 1 } }
)
This does have it's own specific output via the rules of mapReduce, which should be evident in the construct of the "unique" _id value to emit:
"results" : [
{
"_id" : "59e4144331be3474a2f28a92_0",
"value" : {
"user_id" : 123.0,
"score_original" : 65.0,
"score_improvement" : 6.0,
"material" : "A"
}
},
{
"_id" : "59e4144331be3474a2f28a92_1",
"value" : {
"user_id" : 123.0,
"score_original" : 71.0,
"score_improvement" : 8.0,
"material" : "B"
}
},
{
"_id" : "59e4144331be3474a2f28a92_2",
"value" : {
"user_id" : 123.0,
"score_original" : 79.0,
"score_improvement" : 1.0,
"material" : "C"
}
}
],
You should note that aside from being far less complex in implementation there is also actual no "reducer" function defined at all. Which should also lead the inevitable conclusion here.
Iterate the cursor
This really is just a basic cursor iteration and expansion you are asking for, so that is all you really need to do. Which means working from the base defined in out mapper function as a simple shell abstraction:
db.getCollection('junk').find().forEach(d => {
for (var i=0; i < d.scores.length-1; i++) {
printjson({
"user_id": d.user_id,
"score_original": d.scores[i],
"score_improvement": d.scores[i+1] - d.scores[i],
"material": d.materials[i]
})
}
})
Which gives the output as desired:
{
"user_id" : 123,
"score_original" : 65,
"score_improvement" : 6,
"material" : "A"
}
{
"user_id" : 123,
"score_original" : 71,
"score_improvement" : 8,
"material" : "B"
}
{
"user_id" : 123,
"score_original" : 79,
"score_improvement" : 1,
"material" : "C"
}
And it really is that simple.
The base lesson here is that "whilst you can" ask a database to do complicated things, unless it actually results in a significant reduction in the data load returned from the server, then the usual best case is to simply process the data in native client code instead.
Even if the presented data in the question was obtained from some other aggregation operation, it would still generally be better at this stage to simply iterate the cursor result for the final transformation.
And if the transformation where required for further aggregation operations, then by all means follow the first process. However if the data presented is actually obtained by aggregation already and there is a need to transform in further aggregation, then you probably should examine the existing aggregation process you have, since you may not even need the intermediate state with multiple arrays, which is where most of the complexity comes from.

Find document with array containing the maximum occurence of a specific value

I have documents like the this
{ "_id" : ObjectId("5755d81e2935fe65f5d167aa"), "prices" : [ 23, 11, 2, 3, 4, 1, 232 ] },
{ "_id" : ObjectId("5755d81e2935fe65f5d167ab"), "prices" : [ 99, 3, 23, 23, 12 ] },
{ "_id" : ObjectId("5755d81e2935fe65f5d167ac"), "prices" : [ 999, 12, 3, 4, 4, 4, 4, 4, 123 ] },
{ "_id" : ObjectId("5755d81e2935fe65f5d167ad"), "prices" : [ 24, 3, 4, 5, 6, 7, 723 ] }
and I want to find the document with array 'prices' containing the highest amount of digit 4, which in my case is the third document. Is there any way to query it?
Starting from MongoDB 3.2, we can $project our documents and use the $size and the $filter operator to return the "count" of the number 4 in each array. From there we need to $group using that "value" and use the $push accumulator operator to return an array of the documents that have same "maximum". Next you $sort your documents by _id and use $limit to return the documents with the maximum occurrence of 4.
db.collection.aggregate(
[
{ "$project": {
"prices": 1,
"counter": {
"$size": {
"$filter": {
"input": "$prices",
"as": "p",
"cond": { "$eq": [ "$$p", 4 ] }
}
}
}
}},
{ "$group": {
"_id": "$counter",
"docs": { "$push": "$$ROOT" }
}},
{ "$sort": { "_id": -1 } },
{ "$limit": 1 }
]
)

Check if embed exists - aggregation framework mongodb

This is my test collection:
>db.test.find()
{
"_id": ObjectId("54906479e89cdf95f5fb2351"),
"reports": [
{
"desc": "xxx",
"order": {"$id": ObjectId("53fbede62827b89e4f86c12e")}
}
]
},
{
"_id": ObjectId("54906515e89cdf95f5fb2352"),
"reports": [
{
"desc": "xxx"
}
]
},
{
"_id": ObjectId("549067d3e89cdf95f5fb2353"),
"reports": [
{
"desc": "xxx"
}
]
}
I want to count all documents and documents with order, so:
>db.test.aggregate({
$group: {
_id: null,
all: {
$sum: 1
},
order: {
$sum: {
"$cond": [
{
"$ifNull": ["$reports.order", false]
},
1,
0
]
}
}
}
})
and my results:
{
"result" : [
{
"_id" : null,
"all" : 3,
"order" : 3
}
],
"ok" : 1
}
but expected:
{
"result" : [
{
"_id" : null,
"all" : 3,
"order" : 1
}
],
"ok" : 1
}
It makes no difference what I'll put - "$reports.order", "$reports.xxx", etc, aggregation framework check only if the field reports exists, ignores embed.
$ifNull and $eq dosn't work with embeded documents?
Is any way to do something like this
db.test.find({"reports.order": {$exists: 1}})
in aggregation framework?
Sorry for my english and I hope that you understood what I want to show you :)
I think it doesn't work because the field "reports" contain an array, not an object.
I mean, your aggregation works as you expect in this collection:
>db.test.find()
{
"_id": ObjectId("54906479e89cdf95f5fb2351"),
"reports":
{
"desc": "xxx",
"order": {"$id": ObjectId("53fbede62827b89e4f86c12e")}
}
},
{
"_id": ObjectId("54906515e89cdf95f5fb2352"),
"reports":
{
"desc": "xxx"
}
},
{
"_id": ObjectId("549067d3e89cdf95f5fb2353"),
"reports":
{
"desc": "xxx"
}
}
Note that I removed the "[" and "]", so now it's an object, not an array (one-to-one relation).
Because you have array inside the "report" field, you need to unwind the array to output one document for each element. I suppose that if you have two "order" fields inside the "reports" array, you only wants to count it once. I mean:
"reports": [
{
"desc": "xxx",
"order": {"$id": ObjectId("53fbede62827b89e4f86c12e")},
"order": "yyy",
}
]
Should only count as one for the object final "order" sum.
In this case, you need to unwind, group by _id (because the previous example outputs two documents for the same _id) and then group again to count all documents:
db.test.aggregate([
{$unwind: '$reports'},
{$group:{
_id:"$_id",
order:{$sum:{"$cond": [
{
"$ifNull": ["$reports.order", false]
},
1,
0
]
}
}
}},
{$group:{
_id:null,
all:{$sum:1},
order: {
$sum:{
"$cond": [{$eq: ['$order', 0]}, 0, 1]
}
}
}}])
Maybe there is a shorter solution, but this works.

MongoDB min/max aggregation

I've got documents with this simplified schema :
{
positon: 10,
value: 5,
count: 3
}
What I'd like to compute, is to group those documents by position and find the maximum value where the count is greater than 4 but with value less than the minimum value where the count is less than 4.
Here what I've done, but it does not work :
{ $group: {
_id: {
position: "$position",
},
result: {$max: { $cond: [ {$and: [ {$gte: ["$count", 4]},
{$lt: ["$value", {$min: { $cond: [ {$lt: ["$count", 4]},
{ value: "$value" },
10]
}
}]
}]},
{ value: "$value", nb: "$count"},
0]
}
}
}
}
I am said that $minis an invalid operator and I cant figure out how to write the right aggregation function. Would it be better to run a mapreduce ?
If for example I have those documents
{Position: 10, value: 1, count 5}
{Position: 10, value: 3, count 3}
{Position: 10, value: 4, count 5}
{Position: 10, value: 7, count 4}
I'd like the reslt to be
{Position: 10, value: 1, count 4}
As it is the maximum of 'value' where count is greater than 4 but also as there is a value of 3 that has only 3 counts so that the value 4 is not what I'm looking for.
That is a bit of a mouthful to say the least but I'll have another crack at explaining it:
You want:
For each "Position" value find the document whose "value" is less than the the largest "value" of the document with a "count" of less than four, whose own "count" is actually greater than 4.
Which reads like a math exam problem designed to confuse you with the logic. But catching that meaning then you perform the aggregation with the following steps:
db.positions.aggregate([
// Separate the values greater than and less than 4 by "Position"
{ "$group": {
"_id": "$Position",
"high": { "$push": {
"$cond": [
{ "$gt": ["$count", 4] },
{ "value": "$value", "count": "$count" },
null
]
}},
"low": { "$push": {
"$cond": [
{ "$lt": ["$count", 4] },
{ "value": "$value", "count": "$count" },
null
]
}}
}},
// Unwind the "low" counts array
{ "$unwind": "$low" },
// Find the "$max" value from the low counts
{ "$group": {
"_id": "$_id",
"high": { "$first": "$high" },
"low": { "$min": "$low.value" }
}},
// Unwind the "high" counts array
{ "$unwind": "$high" },
// Compare the value to the "low" value to see if it is less than
{ "$project": {
"high": 1,
"lower": { "$lt": [ "$high.value", "$low" ] }
}},
// Sorting, $max won't work over multiple values. Want the document.
{ "$sort": { "lower": -1, "high.value": -1 } },
// Group, get the highest order document which was on top
{ "$group": {
"_id": "$_id",
"value": { "$first": "$high.value" },
"count": { "$first": "$high.count" }
}}
])
So from the set of documents:
{ "Position" : 10, "value" : 1, "count" : 5 }
{ "Position" : 10, "value" : 3, "count" : 3 }
{ "Position" : 10, "value" : 4, "count" : 5 }
{ "Position" : 10, "value" : 7, "count" : 4 }
Only the first is returned in this case as it's value is less than the "count of three" document where it's own count is greater than 4.
{ "_id" : 10, "value" : 1, "count" : 5 }
Which I am sure is what you actually meant.
So the application of $min and $max really only applies when getting discrete values from documents out of a grouping range. If you are interested in more than one value from the document or indeed the whole document, then you are sorting and getting the $first or $last entries on the grouping boundary.
And aggregate is much faster than mapReduce as it uses native code without invoking a JavaScript interpreter.