I have got a collection with the following schema :
{OrderId : id, CustomerId : id, amount: Number, otherPayers : [{name : String, amount : Number}]}
I would like to make an average of the "amount" of all orders for a customerId, and I can do that with aggregate by grouping on customerId with avg on amount.
Now, I'd like the output "amount" field to be not only the avg of "amount", but also of the "amount" field of the "otherPayers" field.
I have had a look into mapreduce but I can't get it to work.
Any idea? In SQL it would have been quite simple with a subquery.
I'm guessing you want to average together all the amounts inside the otherPayers array plus the amount field on the top level of the document. Here is how you would do it with aggregation framework:
unwind = { $unwind : "$otherPayers" };
group = { $ group : { _id : "$CustomerId",
amt : { $first : "$amount" },
count : { $sum : 1 },
total : { $sum : "$otherPayers.amount" }
} };
project = { $project : { _id : 0,
CustomerId : "$_id",
avgPaid : { $divide : [
{ $add : [ "$total", "$amt" ] },
{ $add : [ "$count", 1 ] }
] }
} };
db.collection.aggregate(unwind, group, project);
Related
I have a Mongoose schema which is like this:
const mySchema = new mongoose.Schema(
{
_id: String,
coin: String,
closeTime: Number,
volume: Number,
}
I have a bunch of different coins. Is it possible to get the latest document for each unique coin I have based on the closeTime?
Help would be much appreciated! Thanks.
You can use aggregation to sort by latest/newest closeTime, group by coin then getting the first document of that group:
mySchema.aggregate([
{ $sort: { closeTime: -1 } },
{ $group: { _id: "$coin", latest: { $first: "$$ROOT" } } }
])
This is sorting with numeric closeTime in descending order, getting the first/latest document and putting its data into a property called latest. This should create results like:
[{
"_id" : "2",
"latest" : {
"_id" : ObjectId("6149f106742bb30e2529c453"),
"coin" : "foo",
"closeTime" : 5,
"volume" : 1
}
}
{
"_id" : "1",
"latest" : {
"_id" : ObjectId("6149f111742bb30e2529c45f"),
"coin" : "bar",
"closeTime" : 4,
"volume" : 1
}
}]
You can take this one step further with other aggregation stages to extract/project the underlying coin document.
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 data by dates? I have some docs in mongo that looks something like this: yes i have added my actuall mongo doc and my code too
{
"_id" : ObjectId("58c0e32161ccc654160b776a"),
"consumer_id" : ObjectId("579f03069b49a0840409df83"),
"user_id" : "579f034c9b49a0840409df85",
"values" : [
{
"date" : "2017/2/9",
"point" : 1
},
{
"date" : "2017/2/10",
"point" : -1
},
{
"date" : "2017/2/11",
"point" : -1
}
]
}
{
"_id" : ObjectId("58c0e3db61ccc654160b776b"),
"consumer_id" : ObjectId("579f03069b49a0840409df83"),
"user_id" : "579ef6f5a15b0eac1332034e",
"values" : [
{
"date" : "2017/2/9",
"point" : 1
},
{
"date" : "2017/2/10",
"point" : 1
},
{
"date" : "2017/2/11",
"point" : -1
}
]
}
I'd like to be able to count the no of points by date
my code is like this
var array = [];
var array2 = [];
db.coll.find({}).toArray(function(err, result) {
result.map(function(data) {
array.push(data.values)
})
You would use following :
db.your_collection.aggregate([
{ $unwind : '$values'},
{ $group : {
_id : '$values.date',
point : { $sum : '$values.point' }
}
}
]);
Which will give you below result :
{ "_id" : "2017/2/11", "point" : -2 }
{ "_id" : "2017/2/10", "point" : 0 }
{ "_id" : "2017/2/9", "point" : 2 }
However, its always good to have date stored in proper date format and not as a string.
You need to use the sort function from mongodb:
sort({datefield: -1}}
in Mongodb for example
db.products.find().sort({"created_at": 1}) --- 1 for asc and -1 for desc
in nodejs for example:
collection.find().sort({datefield: -1}, function(err, cursor){...});
You need to perform a map/reduce
I assume your array of objects are stored in the data field of your documents, themselves in an items collection.
// map function which pushes the points in an array associated to the given date date
var mapDataPoints = function() {
for (var i=0; i<this.data.length; i++) emit(this.data[i].date, this.data[i].points);
};
// reduce function, which sums the array of points for a given date
var reduceDataPoints = function(dateId, points) {
return Array.sum(points);
};
// map-reduce operation over the collection, which takes each document and apply the map function, then run the reduce function on the result of the map operation
// result is stored in a new collection, called pointsByDate
db.items.mapReduce(
mapDataPoints,
reduceDataPoints,
{ out: "pointsByDate" }
)
// pointsByDate can be queried just like any collection
db.pointsByDate.find({ "_id": "2017/2/10" });
The following code works for your problem.
db.Stack.aggregate([
{$unwind: "$values"},
{$group: {
_id: "$values.date",
sum: {$sum: "$values.point"}
}
}
])
This is the output for your code
/* 1 */
{
"_id" : "2017/2/11",
"sum" : -2.0
}
/* 2 */
{
"_id" : "2017/2/10",
"sum" : 0.0
}
/* 3 */
{
"_id" : "2017/2/9",
"sum" : 2.0
}
{
"_id": ObjectId("53ab1d2c256072374a5cc63f"),
"title": "10% Off",
"endDate": "2015-05-08",
"limit" : "limited",
"redemptions": [
"1f7f5f96be3a",
"kf40vksk03ps"
]
}
{
"_id": ObjectId("53ab1d2c25607sfdgs74a5cc63f"),
"title": "20% Off",
"endDate": "2015-06-07",
"limit" : "unlimited",
"redemptions": [
"1f7f5f96be3a",
"1f7f5f96be3a",
"kf40vksk03ps"
]
}
Story: a person can redeem a coupon 2 times. After 2 times, don't return it.
How can I check that a value appears less than 2 times??
Wish it was as easy as:
{ 'redemptions' : { $exists: true }, $where : 'this.redemptions.$.1f7f5f96be3a.length < 2' }
How can I get a count for how many times a specific value is in an array and compare on that?
Edit
So to add some fun. I updated my schema, so I need to put that into a conditional. If limit = 'unlimited' { return record } if limit = 'limited' { return only if array has less than 2 values = '1f7f5f96be3a'
You can do what you want with aggregation framework:
db.collection.aggregate([
/* find only documents that have redemption key */
{ $match : { redemptions : { $exists: true }}},
/* unwind the redemptions array so we can filter documents by coupons */
{ $unwind : "$redemptions" },
/* filter only coupons you're looking for */
{ $match : { redemptions : "1f7f5f96be3a"}},
/* group the documents back so we can calculate the count */
{ $group : { _id : "$_id",
title : { $first : "$title" },
endDate : { $first : "$endDate" },
count : {$sum : 1 }}},
/* finally, count the documents that have less than 2 coupons */
{ $match : { count : { $lt :2 }}}
]);
Edit:
You just need to change the $group and last $match stages:
{ $group : { _id : "$_id",
title : { $first : "$title" },
endDate : { $first : "$endDate" },
limit : { $first : "$limit" },
count : {$sum : 1 }}},
/* finally, count the documents that have less than 2 coupons
or have limit "unlimited */
{ $match : { $or : [{ count : { $lt :2 }}, {limit : "unlimited"}]}}
My structure.
User:
{
name: "One",
favoriteWorkouts: [ids of workouts],
workouts: [ { name: "My workout 1" },...]
}
I want to get list of favorits/hottest workouts from database.
db.users.aggregate(
{ $unwind : "$favorite" },
{ $group : { _id : "$favorite" , number : { $sum : 1 } } },
{ $sort : { number : -1 } }
)
This returns
{
"hot": [
{
"_id": "521f6c27145c5d515f000006",
"number": 1
},
{
"_id": "521f6c2f145c5d515f000007",
"number": 1
},...
]}
But I want
{
hot: [
{object of hottest workout 1, object of hottest workout 2,...}
]}
How do you sort hottest data and fill the result with object, not just ids?
You are correct to want to use MongoDB's aggregation framework. Aggregation will give you the output you are looking for if used correctly. If you are looking for just a list of the _id's of all users' favorite workouts, then I believe that you would need to add an additional $group operation to your pipeline:
db.users.aggregate(
{ $unwind : "$favoriteWorkouts" },
{ $group : { _id : "$favoriteWorkouts", number : { $sum : 1 } } },
{ $sort : { number : -1 } },
{ $group : { _id : "oneDocumentWithWorkoutArray", hot : { $push : "$_id" } } }
)
This will yield a document of the following form, with the workout ids listed by popularity:
{
"_id" : "oneDocumentWithWorkoutArray",
"hot" : [
"workout6",
"workout1",
"workout5",
"workout4",
"workout3",
"workout2"
]
}