How to aggregate array of document in MongoDB - mongodb

I have the following schema:
var orderSchema = new mongoose.Schema({
deleted: Boolean,
clientName: String,
clientPhone: String,
products: [{
name: String,
quantity: Number,
price: Number
}],
observations: String,
date: {
type: Date,
default: Date.now
},
seller: String,
orderNumber: Number,
total: Number
});
And I want to make an aggregate query that sums each product and add quantity*price to the total, and return me the total. However, I only know how to do it if I had multiple products, each one as a document, and then sum over all the documents but not this.

You can do this with $reduce :
db.collection.aggregate([{
$addFields: {
"total": {
$reduce: {
input: "$products",
initialValue: '$total',
in: { $add: [{ $multiply: ["$$this.quantity", "$$this.price"] }, '$$value'] }
}
}
}
}])
Test : MongoDB-Playground

Related

Mongoose/Mongodb Aggregate - group and average multiple fields

I have a Post model with 2 fields : date and rating. How would I go about getting an average aggregate rating for each date? So group by date first and then average the rating across all posts for that date. I need to do this within mongoose but their docs are so difficult to understand.
const PostSchema = new Schema({
date: {
type: String,
default: getToday() //this is just a new Date() formatted
},
rating: {
type: Number,
required: true
}
},
)
This gives me the average across all dates but I can't figure out how to filter it by date:
Post.aggregate([
{ $group: { _id: null, avgRating: { $avg: '$rating' }}}
])
.then(function (res) {
console.log(res[0]["avgRating"]);
})
This worked for me:
Post.aggregate([
{ $group: { _id: "$date", avgRating: { $avg: '$rating' }}}
]).
then(function (res) {
console.log(res);
})
Output:
[
{ _id: 'Aug 18, 2021', avgRating: 3.0212234706616727 },
{ _id: 'Aug 19, 2021', avgRating: 2.9680319680319682 },
{ _id: 'Aug 20, 2021', avgRating: 3.023976023976024 },
{ _id: 'Aug 17, 2021', avgRating: 2.9600665557404326 },
{ _id: 'Aug 21, 2021', avgRating: 3.072661217075386 }
]
BUT it would be great if I could somehow filter this based on other factors. For example, each post has an author (reference to User model). How would I go about filtering based on the author's country.name or gender?
User model:
const userSchema = new Schema({
email: {
type: String,
required: true,
unique: true
},
birthday: {
type: Date,
required: true,
},
gender:{
type: String,
required: true
},
country:{
name: {
type: String,
required: true
},
flag: {
type: String,
// default: "/images/flags/US.png"
}
},
avatar: AvatarSchema,
displayName: String,
bio: String,
coverColor: {
type: String,
default: "#343a40"
},
posts: [
{
type: Schema.Types.ObjectId,
ref: "Post"
}
],
comments: [
{
type: Schema.Types.ObjectId,
ref: "Comment"
}
],
postedToday: {
type: Boolean,
default: false
},
todaysPost: {
type: String
}
})
Something like this
Post.aggregate([
{$match: {"date": today}},
{$group: {_id: {"country": "$author.country.name"}, avgRating: {$avg: "$rating"}}}
]).then(function(res) {
console.log(res)
})

create index for mongodb collection

I have a MongoDB collection
{
id: String,
country: String,
createdAt: Date,
...
}
What is the right indices for the collection using this aggregate?
.aggregate([
{ $match: { country: "US" } },
{ $sort: { createdAt: -1 } },
{
$group: {
_id: { id: '$id' },
totalPrice: { $first: '$totalPrice' },
},
},
])
I have these indices on the schema
schema.index({ country: 1 });
schema.index({ createdAt: 1 });
schema.index({ country: 1, id: 1, createdAt: 1 });
But I think something is not healty

How to calculate average rating of my product in mongodb?

This is the query I used to get the average of rating, but the result was null:
db.products.aggregate([{$unwind: "$reviews"}, {$group: {_id: "$reviews", ratingAvg: {$avg: "$rating"}}}])
This is my product schema:
category: String,
name: String,
price: Number,
image: String,
description: String,
stock: Number,
reviews: [
{
type: mongoose.Schema.Types.ObjectID, ref: 'Review'
}
})
This is my review schema:
text: String,
rating: Number,
// author: {
// id: {type: mongoose.Schema.Types.ObjectID, ref: 'User'},
// name: String
// }
author: String
})
Every time I run this query I get:
{ "_id" : ObjectId("5f79d1b4b4b3c1061f1fbe52"), "ratingAvg" : null }
{ "_id" : ObjectId("5f79d1a5b4b3c1061f1fbe51"), "ratingAvg" : null }
{ "_id" : ObjectId("5f79d196b4b3c1061f1fbe4f"), "ratingAvg" : null }
It is showing ratingAvg as"null" whereas I have ratings for that product in my database.
$lookup helps to join two collections.
[
{
"$lookup": {
"from": "Review",
"localField": "reviews",
"foreignField": "_id",
"as": "reviews"
}
},
{
$unwind: "$reviews"
},
{
$group: {
_id: null,
ratingAvg: {
$avg: "$reviews.rating"
}
}
}
]
Then you can easily find the average using $avg along with $group after destructure the array using $uniwnd
Working Mongo playground

Group document mongodb

I'm trying to group document with mongodb but couldn't figure out how.
I have a document looks like this
{
_id: ObjectId('12345'),
username: 'asd',
region: 'zxc',
amount: 500,
type: 'car',
brand: 'vent',
order: 2
},
{
_id: ObjectId('98283'),
username: 'asd',
region: 'zxc',
amount: 1500,
type: 'car',
brand: 'dinosaur',
order: 1
}
And I want to group the document by username, region, type and make a new sub document from the result and order the sub-document ascending by the order. Also calculate the amount as a totalAmount. Which looks like this.
{
username: 'asd',
region: 'zxc',
type: 'car',
cart: [
{
brand: 'dinosaur',
amount: 1500
},
{
brand: 'vent',
amount: 500
}
],
totalAmount: 2000
}
I could only do this so far
db.test.aggregate([
{
$group: {
_id: {username: "$username"},
region: {$first: "$region"},
type: {$first: "$type"},
totalAmount: {$sum: "$amount"}
}
}
])
Thanks
You should put all of the fields you want to group on in the _id.
Use $push to collect values into an array, and $sum to compute the total:
db.collection.aggregate([
{
$group: {
_id: {
username: "$username",
region: "$region",
type: "$type"
},
cart: {
$push: {
brand: "$brand",
amount: "$amount"
}
},
total: {
$sum: "$amount"
}
}
}
])
Playground

mongo db aggregate keep fields after group

I have this aggregate:
[
{
$match: {_id: new ObjectId('xxx')}
},
{
$unwind: "$users"
},
{
$group: {
_id: '$users.status',
count: {$sum: 1}
}
},
{
$project: {
name: 1,
field1: 1,
field2: 1,
count: '$count'
}
}
]
The schema:
{
name: String,
field1: String,
field2: Schema.Types.Mixed,
users: [{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
status: String
}]
}
The expected result:
{
name: 'NY',
field1: 'example',
field2: 'example2',
statuses: [
{
_id: 'ONLINE',
count: 20
},
{
_id: 'OFFLINE',
count: 120
},
{
_id: 'OTHER',
count: 230
}
]
}
This way I get group result just for the users statuses, and not for the other fields of the original object.