MongoDb How to do complex aggregation to an order Model - mongodb

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

Related

MongoDB aggregate. Create new groups for non-existing items

My collection of documents contains information about users, their sessions and CRUD operations they performed during these sessions:
{
user_id: '1',
sessions: [
{
actions: [
{
type: 'create',
created_at: ISODate('2020-01-01T00:00:00'),
},
{
type: 'read',
created_at: ISODate('2022-01-01T00:00:00'),
},
{
type: 'read',
created_at: ISODate('2021-01-01T00:00:00'),
}
],
}
]
}
I need to get a summary for each user, which includes the amount of CRUD operations and the date of the last one:
{
user_id: '1',
actions: [
{
type: 'create',
last: ISODate('2020-01-01T00:00:00'),
count: 1,
},
{
type: 'read',
last: ISODate('2022-01-01T00:00:00'),
count: 2,
},
// Problematic part:
{
type: 'update',
last: null,
count: 0,
},
{
type: 'delete',
last: null,
count: 0,
},
]
}
I came up with this solution:
db.users.aggregate([
{$unwind:'$sessions'},
{$unwind:'$sessions.actions'},
{
$group:{
_id:{user_id:'$user_id', type:'$sessions.actions.type'},
last:{$max:'$sessions.actions.created_at'},
count:{$sum:1},
}
},
{
$group:{
_id:{user_id:'$_id.user_id'},
actions:{$push:{type:'$_id.type', last:'$last', count:'$count'}}
}
},
{
$project:{
_id:0,
user_id: '$_id.user_id',
actions: '$actions'
}
}
])
The problem here is that I cannot figure out, how can I add missing actions, like in 'update' and 'delete' in the example above
Try this,
db.collection.aggregate([
{
$unwind: "$sessions"
},
{
$unwind: "$sessions.actions"
},
{
$group: {
_id: {
user_id: "$user_id",
type: "$sessions.actions.type"
},
last: {
$max: "$sessions.actions.created_at"
},
count: {
$sum: 1
},
}
},
{
$group: {
_id: {
user_id: "$_id.user_id"
},
actions: {
$push: {
type: "$_id.type",
last: "$last",
count: "$count"
}
}
}
},
{
$project: {
_id: 0,
user_id: "$_id.user_id",
actions: {
"$function": {
"body": "function(doc) { const ops = {read:0, delete:0, update: 0, create: 0}; const actions = doc.actions; actions.forEach(action => { ops[action.type] = 1 }); Object.keys(ops).filter(key => ops[key] === 0).forEach(key => actions.push({count: 0, last: null, type: key})); return actions }",
"args": [
"$$ROOT"
],
"lang": "js"
}
},
}
},
])
Here, we use $function and provide a small JS function to populate the missing entries.
Playground link.

update a nested array of strings inside an array of objects in a schema in mongo

I´m trying to update my database schema in its user topic .
Its schema would look like this:
name: [Object],
nickname: [Object],
email: [Object],
password: [Object],
image: [Object],
googleFlag: [Object],
groupOfChats: [Array],
role: [Object],
messages: [Array],
userState: [Object],
friends: [Array]
Where the item to modify would be the groupOfChats that is an array that contains several objects and on the objects there is an item 'memberId' which is a array of string ,being this last one the one i want to access to modify:
groupOfChats: [
{
idGroup: { type: String, required: true },
nameGroup: { type: String, required: true },
membersId: [{ type: String, required: false }],
groupCreatorId: { type: String, required: true },
messages: [{ type: String, required: false }],
groupImage: { type: String, required: false },
},
],
Traying to access that membersId item in a specific group i just tried to set this:
let friendsAddedIdOnly =["des","pa","cito"];
let userChatGroupUpdate = User.updateOne(
{
_id: payload.idUserCreatorGroup,
"groupOfChats.idGroup": payload.groupId,
},
{ $push: { "membersId.$[]": friendsAddedIdOnly} },
{ new: true }
);
(await userChatGroupUpdate).save();
a view of my mongo database would be like this:
Edit:
Old asnwer wasn't working you're right. But you can use below aggregation
db.collection.aggregate([
{
"$match": {
_id: payload.idUserCreatorGroup
}
},
{
"$set": {
"groupOfChats.0.membersId": {
"$reduce": {
"input": "$groupOfChats.membersId",
"initialValue": friendsAddedIdOnly,
"in": {
"$concatArrays": [
"$$this",
"$$value"
]
}
}
}
}
},
{
"$set": {
"groupOfChats.0": {
$concatArrays: [
{
$slice: [
"$groupOfChats.0",
1
]
},
{
$slice: [
"$groupOfChats.0",
{
$add: [
1,
1
]
},
{
$size: "$groupOfChats.0"
}
]
}
]
}
}
}
])
Playground
Gues this is the right approach. By the way thanks to #Murat Colyaran for being so helpful:
User.updateOne(
{
_id: payload.idUserCreatorGroup,
"groupOfChats.idGroup": payload.groupId,
},
{
$push: {
"groupOfChats.$.membersId": { $each: friendsAddedIdOnly },
},
}
);
(await userChatGroupUpdate).save();

How to $sum or $subtract in mongodb aggregation depending on a certain condition

Assume I have a model that looks like this
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
points: {
type:Number,
required: true,
},
wonOrLost:{
type: String,
required: true,
enum: ['won', 'lost', 'noResults'],
default: 'noResults',
},
Then I have aggregation code that looks like this:
let _points = await Points.aggregate([
{ $match: {
userId: user._id ,
wonOrLost: { $ne: "noResults" }
}},
{ $group: {
_id: "$userId",
// here(totalPoints) I want to add points if wonOrLost == 'won' and subtract if wonOrLost == 'lost'
totalPoints: { $sum: "$points" },
}}
])
Here is how I would do it using $cond, the strategy would be to multiply the points by -1 if the match was lost before summing.
let _points = await Points.aggregate([
{
$match: {
userId: user._id,
wonOrLost: {$ne: 'noResults'}
}
},
{
$group: {
_id: '$userId',
totalPoints: {$sum: {$cond: [{$eq: ["$wonOrLost", "won"]}, '$points', {$multiply: ['$points', -1]}]}},
}
}
]);

Returning unique mongodb documents using distinct not working

Each message document has a messageTrackingId. I want to return all the messages, but exclude documents that have the same messageTrackingId. So for example, if I had 4 documents in my table and 3 of them have the same messageTrackingId value, then the Messages.find() should only return 2 documents.
I'm trying to use distinct to only return the unique documents so I don't get duplicates with the same messageTrackingId. Currently postman is returning no documents.
if I changed
Messages.find({query}).distinct('messageTrackingId')
to
Messages.find(query)
then it returns all the recipientId's documents. but when I add distinct, I get no results.
app.get('/api/messages',(req, res, next)=>{
query = {};
inbox = false;
messageId = false;
if(req.query.recipientId){
query = { recipientId: req.query.recipientId }
inbox = true;
Messages.aggregate(// Pipeline
[
// Stage 1
{
$group: {
_id: "$messageTrackingId",
message : { $addToSet: '$message' },
recipientId : { $addToSet: '$recipientId' },
creator : { $addToSet: '$creator' },
messageTrackingId : { $addToSet: '$messageTrackingId' },
}
},
// Stage 2
{
$project: {
_id: 1,
message: { $arrayElemAt: ["$message", 0 ] },
recipientId: { $arrayElemAt: ["$recipientId", 0 ] },
creator: { $arrayElemAt: ["$creator", 0 ] },
messageTrackingId: { $arrayElemAt: ["$messageTrackingId", 0 ] }
}
}
])
messages model
const mongoose = require("mongoose");
const uniqueValidator = require("mongoose-unique-validator");
const messagingSchema = mongoose.Schema({
creator: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
recipient: { type: String, required: true },
recipientId: { type: String, required: true },
message: { type: String, required: true },
subject: { type: String, required: true },
creationDate: { type: Date, required: true },
messageTrackingId: { type: String }
// readDate: { type: Date, required: true }
});
module.exports = mongoose.model("Messages", messagingSchema);
distinct will return distinct fields which is not what you want.
You will need to use aggregation and group by the messageTrackingId, then project grabbing the first message content etc you want:
Given sample data like:
{ "messageTrackingId" : 1, "message" : "hello" }
{ "messageTrackingId" : 1, "message" : "hello" }
{ "messageTrackingId" : 1, "message" : "bye" }
{ "messageTrackingId" : 2, "message" : "bye" }
{ "messageTrackingId" : 2, "message" : "bye" }
{ "messageTrackingId" : 1, "message" : "hello" }
In MongoDB:
db.getCollection("messages").aggregate(
// Pipeline
[
// Stage 1
{
$group: {
_id: "$messageTrackingId",
message : { $addToSet: '$message' }
}
},
// Stage 2
{
$project: {
_id: 1,
message: { $arrayElemAt: ["$message", 0 ] }
}
},
]);
To use in mongoose, simply using the aggregate function on your model:
Using Mongoose
const result = await Message.aggregate(// Pipeline
[
// Stage 1
{
$group: {
_id: "$messageTrackingId",
message : { $addToSet: '$message' }
}
},
// Stage 2
{
$project: {
_id: 1,
message: { $arrayElemAt: ["$message", 0 ] }
}
},
]);
UPDATE AFTER COMMENTS
If you need to query for a given messageTrackingId, then add $match as the first stage of the pipeline:
const result = await Message.aggregate(
[
{
$match: {
messageTrackingId: {$eq: 2}
}
},
{
$group: {
_id: "$messageTrackingId",
message : { $addToSet: '$message' }
}
},
{
$project: {
_id: 1,
message: { $arrayElemAt: ["$message", 0 ] }
}
},
]);

Check follow status in aggregate mongo / mongoose

I have this schema for users where followers/followed is array and the reference the same schema
var userSchema = new Schema({
username: { type: String, unique: true, trim: true, required: true },
password: { type: String, required: true },
followers: [{ type: Schema.Types.ObjectId, ref: "users" }],
followed: [{ type: Schema.Types.ObjectId, ref: "users" }],
registered: { type: Date, default: Date.now },
admin: { type: Number, default: 0 }
});
What I am looking for to return the follow status, if the _id is contains in followed array give me for example follow_status: 1
[
{
$match: { username: new RegExp(username, "i") }
},
{
$unwind: "$followers"
},
{
$lookup: {
from: "users",
localField: "followers",
foreignField: "_id",
as: "info"
}
},
{
$unwind: "$info"
},
{
$project: {
info: {
_id: 1,
username: 1,
avatar: { $ifNull: ["$avatar", ""] },
fullname: { $ifNull: ["$fullname", ""] }
}
}
},
{
$replaceRoot: { newRoot: "$info" }
},
{
$limit: 1000
}
]
Current pipeliens result
[
{
"_id": "5a906653f52e66c9c7a23cb6",
"username": "User1"
},
{
"_id": "5a908eb564a726cf8ec7e0a3",
"username": "User2"
}
]