Mongoose/Mongodb Aggregate - group and average multiple fields - mongodb

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)
})

Related

MongoDb How to do complex aggregation to an order Model

Hello Guys This is my Order Model :
const OrderSchema = new Schema({
orderItems: {
name: { type: String, required: true },
qty: { type: Number, required: true },
image: { type: String, required: false },
price: { type: Number, required: true },
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true,
},
},
totalPrice: { type: Number, required: true },
paymentMethod: { type: String, required: true },
paymentResult: {
status: { type: String, default: 'Waiting For Payment' }
},
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
isPaid: { type: Boolean, default: false },
paidAt: { type: Date },
OrderResult: {
status: { type: String, default: 'Waiting For Payment' }
},
isDelivered: { type: Boolean, default: false },
},{timestamps : true})
I did do This aggregation to get Users Orders Summary :
const usersOrderSummary = await Order.aggregate(
[
{$match:{} },
{$group: {_id:"$user", TotalSpent: {$sum:"$totalPrice"},TotalOrders: { $sum: 1 }}},
]
)
The Result Of this aggregation is an array of:
{
"usersOrderSummary": [
{
"_id": "6216687c0e0d9122f710a1a6",
"TotalSpent": 0.9,
"TotalOrders": 8
},
{
"_id": "628e4b96a7fd3bad9482a81c",
"TotalSpent": 9.18,
"TotalOrders": 53
}
]
}
I want your Help to do an aggregation to get this result:
I want to to do aggregation to collect the sum of completed orders and the sum of the amount that spent in this completed order
the condtion of completed order so i can only considered an order completed when
isDelivered:true
please check the schema above so it would be clear to know how order considered as completed
{
"usersOrderSummary": [
{
"_id": "6216687c0e0d9122f710a1a6",
"TotalSpent": 0.9,
"TotalOrders": 8,
"TotalCompletedOrderSpent": 0.1,
"TotalCompletedOrders": 1,
},
{
"_id": "628e4b96a7fd3bad9482a81c",
"TotalSpent": 9.18,
"TotalOrders": 53
"TotalCompletedOrderSpent": 4,
"TotalCompletedOrderSpent": 2,
}
]
}
Use $cond
db.collection.aggregate([
{
$match: {}
},
{
$group: {
_id: "$user",
TotalSpent: { $sum: "$totalPrice" },
TotalOrders: { $sum: 1 },
TotalCompletedOrderSpent: {
$sum: {
$cond: {
if: { $eq: [ "$isDelivered", true ] },
then: "$totalPrice",
else: 0
}
}
},
TotalCompletedOrders: {
$sum: {
$cond: {
if: { $eq: [ "$isDelivered", true ] },
then: 1,
else: 0
}
}
}
}
}
])
mongoplayground

Get extra field in model summing all records from lookup document

Having this model:
const matchSchema = mongoose.Schema({
location: {type: mongoose.Types.ObjectId, ref: 'Location'},
datetime: Date,
teamAName: String,
teamBName: String,
teamA: [{type: mongoose.Types.ObjectId, ref: 'Player'}],
teamB: [{type: mongoose.Types.ObjectId, ref: 'Player'}],
teamAScore: {type: Number, default: 0},
teamBScore: {type: Number, default: 0},
pichichi: [{type: mongoose.Types.ObjectId, ref: 'Player'}],
mvp: {type: mongoose.Types.ObjectId, ref: 'Player', default:null},
});
"teamA" and "teamB" are lists containing the "_id" of every player. When retrieving a player, I want to retrieve the number of matches that he/she have played. How can I do that? Below my query just retrieving fields from model "Player"
class PlayerController {
getAll(req, res) {
Player.find()
.sort('firstname')
.exec(function(err, players) {
res.send(players);
});
}
}
So, instead of just having this list:
[
{
_id: new ObjectId("6232395d08663294b412d6a1"),
firstname: 'Sam',
lastname: 'Credington',
__v: 0
},
{
_id: new ObjectId("622479f39be8118a52af70e5"),
firstname: 'Santi',
lastname: 'Futsal',
__v: 0
},
{
_id: new ObjectId("6232399608663294b412d6b9"),
firstname: 'Tom',
lastname: 'Hendry',
__v: 0
}
]
I would like to have the amount of matches that every player played:
[
{
_id: new ObjectId("6232395d08663294b412d6a1"),
firstname: 'Sam',
lastname: 'Credington',
matches:6,
__v: 0
},
{
_id: new ObjectId("622479f39be8118a52af70e5"),
firstname: 'Santi',
lastname: 'Futsal',
matches:8,
__v: 0
},
{
_id: new ObjectId("6232399608663294b412d6b9"),
firstname: 'Tom',
lastname: 'Hendry',
matches: 2,
__v: 0
}
]
Here's one way you could do it.
db.players.aggregate([
{
"$lookup": {
"from": "matches",
"let": { "myId": "$_id" },
"pipeline": [
{
"$match": {
"$expr": {
"$in": [ "$$myId", { "$setUnion": [ "$teamA", "$teamB" ] } ]
}
}
},
{ "$count": "numMatches" }
],
"as": "matchCount"
}
},
{
"$set": {
"matches": {
"$ifNull": [ { "$first": "$matchCount.numMatches" }, 0 ]
}
}
},
{ "$unset": "matchCount" }
])
Try it on mongoplayground.net.

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

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 aggregation lookup

I am trying to attempt the following
From a log, find all prizes the user entered
return the order numbers and the title
I am using the mongo aggregation framework.
I match by userId
I group the prizes by the same product and then push order numbers
I want to do a look up to the prizes collection to find the title of the prizes.
The look up is returning no elements
Here is the aggregation code
db.pointslogs.aggregate(
// Pipeline
[
// Stage 1
{
$match: {
"user": ObjectId("5aacff47c67f99103bcbf693")
}
},
// Stage 2
{
$group: {
_id: "$productPurchased",
orderNumber: { $push: "$orderNumber" }
}
},
// Stage 3
{
$unwind: {
path : "$orderNumber",
}
},
// Stage 4
{
$lookup: {
"from" : "prizes",
"localField" : "_id",
"foreignField" : "_id",
"as" : "title"
}
},
]
// Created with Studio 3T, the IDE for MongoDB - https://studio3t.com/
);
Here are the models (points log below)
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var schema = new Schema({
orderNumber: {type: String, required: true},
productPurchased: {type: String, require: true},
answer: {type: String},
user: [{type: Schema.Types.ObjectId, ref: 'User'}]
});
module.exports = mongoose.model('PointsLog', schema);
Prizes model below
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var schema = new Schema({
title: {type: String, required: true},
content: {type: String, required: true},
orderNumber: {type: String, required: true},
imageUrl: {type: String, required: true},
stockQty: {type: Number, required: true},
question: {type: String, required: true},
answers: [{type: String, required: true}],
image: {type: String, required: true},
cost: {type: Number, required: true },
//entries: {type: Int},
//user: {type: Schema.Types.ObjectId, ref: 'User'}
user: [{type: Schema.Types.ObjectId, ref: 'User'}]
});
module.exports = mongoose.model('Prize', schema);
Not sure why lookup is returning nothing
1)You can use the upgraded lookup
Below is an Example->
db.pointslogs.aggregate([
// Stage 1
{
$match: {
"user": ObjectId("5aacff47c67f99103bcbf693")
}
},
// Stage 2
{
$group: {
_id: "$productPurchased",
orderNumber: { $push: "$orderNumber" }
}
},
// Stage 3
{
$unwind: {
path : "$orderNumber",
}
},
// Stage 4
{
$lookup:
{
from: "prizes",
let: { oNumber: "$orderNumber" },
pipeline: [
{ $match:{ $expr: { $eq: [ "$orderNumber", "$$oNumber" ] } }
}
},
{ $project: {
title: 1,
content: 1,
orderNumber: 1,
imageUrl: 1,
stockQty: 1,
question: 1,
answers: 1,
image: 1,
cost: 1,
user: 1
} }
],
as: "orderNumber"
}
}
])