Mongodb $lookup/populate and summarize - node.js

I'm trying to join two schema and summarize the total price.
This is the schema:
const Product = new mongoose.Schema({
name: { type: String, required: true },
price: Number
})
const Order = new mongoose.Schema({
fullname: { type: String, required: true },
address: { type: String, required: true },
products: [
{
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
},
quantity: Number,
},
],
})
I want to create aggregation to get orders with total price.so it could be like:
[
{
fullname: 'jhon doe',
address: 'NY 1030',
products: [
{
product: {
name: 'Piano',
price: 50
},
quantity: 10,
},
],
price: 500
}
]
I try to use aggregation framework without any success, any idea?

Updated
As the question needs the price to be calculated by sum of the multiplication of quantity and product price, It can be done with below code:
db.getCollection('orders').aggregate([
{ $unwind: { path: '$products' } },
{
$lookup: {
from: 'products',
localField: 'products.product',
foreignField: '_id',
as: 'p',
},
},
{ $unwind: { path: '$p' } },
{
$group: {
_id: '$_id',
price: { $sum: { $multiply: ['$p.price', '$products.quantity'] } },
fullname: { $first: '$fullname' },
address: { $first: '$address' },
products: { $push: { product: '$p', quantity: '$products.quantity' } },
},
},
])
----------------------------------------------------------------------------------------------------------------------------
You can use $lookup in aggregation as below:
db.getCollection('orders').aggregate([
{
$lookup: {
from: 'products',
localField: 'products.product',
foreignField: '_id',
as: 'p',
},
},
{ $unwind: { path: '$p' } },
{
$project: {
fullname: 1,
address: 1,
products: {
product: '$p',
quantity: 1,
},
price: 1,
},
},
])

Related

How to find sum in Mongo aggregate method with match in different collection in an array of object?

I am trying to find the sum by matching array of object using mongoose. I have 2 collection such as
const accountSchema = new mongoose.Schema({
groupId: {
type: Number,
required: true
},
account_no: {
type: String,
required: true
},
account_name: {
type: String,
required: true
},
opening_balance: {
type: Number,
default: 0
}
})
And second collection as:
const mongoose = require('mongoose')
const AutoIncrement = require('mongoose-sequence')(mongoose);
const accountJournalSchema = new mongoose.Schema({
journal_no: {
type: Number
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'Users',
required: [true, 'User ID is required.'],
},
groupId: {
type: Number,
required: true
},
date: {
type: Date,
required: true
},
receipt: [
{
account_no: {
type: mongoose.Schema.ObjectId,
ref: 'Accounts',
required: true
},
debit: {
type: Number,
default: 0
},
credit: {
type: Number,
default: 0
},
}
]
})
And my aggregate method is:
await Accounts.aggregate([
{
$match: {
$and: [
{ groupId: {$eq: parseInt(req.params.group_id)} },
{ 'Account_jour.groupId': { $eq: parseInt(req.params.group_id) } }
]
}
},
{ unwind: '$Account_jour' },
{
$lookup: {
from : 'account_journals',
localField: '_id',
foreignField: 'receipt.account_no',
as: 'Account_jour'
}
}
])
I am getting error from the above statement:
Arguments must be aggregate pipeline operators
And after solving the issue I also want to find the sum of debit and credit.
Thank you!!
Try this:
await Accounts.aggregate([
{
$match: {
$and: [{ groupId: { $eq: parseInt(req.params.group_id) } }, { "Account_jour.groupId": { $eq: parseInt(req.params.group_id) } }]
}
},
{ $unwind: '$Account_jour' },
{
$lookup: {
from : 'account_journals',
localField: '_id',
foreignField: 'receipt.account_no',
as: 'Account_jour'
}
}
])

How to aggregate with many conditions on MongoDB. Double $lookup etc

How to display "hardest category" based on in which "study" size of notLearnedWords was the highest. MongoDB Aggregation
I have these 3 models:
Study
WordSet
Category
Study model has reference into WordSet, then WordSet has reference into Category.
And based on Studies i'm displaying statistics.
How i can display "The hardest category" based on size of "notLearnedWords" was the highest?
I don't know on which place i should start with that querying.
For now i display "hardestCategory" as element that is most used.
I think that condition would look something like this:
{ $max: { $size: '$notLearnedWords' } } // size of the study with most notLearnedWords
I would achieve a response like this:
"stats": [
{
"_id": null,
"numberOfStudies": 4,
"averageStudyTime": 82.5,
"allStudyTime": 330,
"longestStudy": 120,
"allLearnedWords": 8
"hardestCategory": "Work" // only this field is missing
}
]
I've tried to do it like this:
const stats = await Study.aggregate([
{ $match: { user: new ObjectID(currentUserId) } },
{
$lookup: {
from: 'users',
localField: 'user',
foreignField: '_id',
as: 'currentUser',
},
},
{
$lookup: {
from: 'wordsets',
let: { wordSetId: '$learnedWordSet' },
pipeline: [
{ $match: { $expr: { $eq: ['$_id', '$$wordSetId'] } } },
{
$project: {
_id: 0,
category: 1,
},
},
{ $unwind: '$category' },
{
$group: {
_id: '$category',
count: { $sum: 1 },
},
},
{ $sort: { count: -1 } },
{ $limit: 1 },
{
$lookup: {
from: 'categories',
localField: '_id',
foreignField: '_id',
as: 'category',
},
},
{
$project: {
_id: 0,
category: { $arrayElemAt: ['$category.name', 0] },
},
},
],
as: 'wordSet',
},
},
{
$group: {
_id: null,
numberOfStudies: { $sum: 1 },
averageStudyTime: { $avg: '$studyTime' },
allStudyTime: { $sum: '$studyTime' },
longestStudy: { $max: '$studyTime' },
allLearnedWords: {
$sum: { $size: '$learnedWords' },
},
hardestCategory: {
$first: {
$first: '$wordSet.category',
},
},
studyWithMostNotLearnedWords: { $max: { $size: '$notLearnedWords' } },
},
},
]);
Study
const studySchema = new mongoose.Schema({
name: {
type: String,
},
studyTime: {
type: Number,
},
learnedWords: [String],
notLearnedWords: [String],
learnedWordSet: {
type: mongoose.Schema.Types.ObjectId,
ref: 'WordSet',
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
});
WordSet
const wordSetSchema = new mongoose.Schema({
name: {
type: String,
},
category: {
type: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Category',
required: true,
},
],
},
});
Category
const categorySchema = new mongoose.Schema({
name: {
type: String,
},
});

Perform $lookup with object value in array from pipeline

So I have 3 models user, property, and testimonials.
Testimonials have a propertyId, message & userId. I've been able to get all the testimonials for each property with a pipeline.
Property.aggregate([
{ $match: { _id: ObjectId(propertyId) } },
{
$lookup: {
from: 'propertytestimonials',
let: { propPropertyId: '$_id' },
pipeline: [
{
$match: {
$expr: {
$and: [{ $eq: ['$propertyId', '$$propPropertyId'] }],
},
},
},
],
as: 'testimonials',
},
},
])
The returned property looks like this
{
.... other property info,
testimonials: [
{
_id: '6124bbd2f8eacfa2ca662f35',
userId: '6124bbd2f8eacfa2ca662f29',
message: 'Amazing property',
propertyId: '6124bbd2f8eacfa2ca662f2f',
},
{
_id: '6124bbd2f8eacfa2ca662f35',
userId: '6124bbd2f8eacfa2ca662f34',
message: 'Worth the price',
propertyId: '6124bbd2f8eacfa2ca662f2f',
},
]
}
User schema
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
Property schema
name: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
location: {
type: String,
required: true,
},
Testimonial schema
propertyId: {
type: ObjectId,
required: true,
},
userId: {
type: ObjectId,
required: true,
},
testimonial: {
type: String,
required: true,
},
Now the question is how do I $lookup the userId from each testimonial so as to show the user's info and not just the id in each testimonial?
I want my response structured like this
{
_id: '6124bbd2f8eacfa2ca662f34',
name: 'Maisonette',
price: 100000,
testimonials: [
{
_id: '6124bbd2f8eacfa2ca662f35',
userId: '6124bbd2f8eacfa2ca662f29',
testimonial: 'Amazing property',
propertyId: '6124bbd2f8eacfa2ca662f34',
user: {
_id: '6124bbd2f8eacfa2ca662f29',
firstName: 'John',
lastName: 'Doe',
email: 'jd#mail.com',
}
},
{
_id: '6124bbd2f8eacfa2ca662f35',
userId: '6124bbd2f8eacfa2ca662f27',
testimonial: 'Worth the price',
propertyId: '6124bbd2f8eacfa2ca662f34',
user: {
_id: '6124bbd2f8eacfa2ca662f27',
firstName: 'Sam',
lastName: 'Ben',
email: 'sb#mail.com',
}
}
]
}
You can put $lookup stage inside pipeline,
$lookup with users collection
$addFields, $arrayElemAt to get first element from above user lookup result
Property.aggregate([
{ $match: { _id: ObjectId(propertyId) } },
{
$lookup: {
from: "propertytestimonials",
let: { propPropertyId: "$_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$propertyId", "$$propPropertyId"] }
}
},
{
$lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
as: "user"
}
},
{
$addFields: {
user: { $arrayElemAt: ["$user", 0] }
}
}
],
as: "testimonials"
}
}
])
Playground

Problem in Mongoose $lookup for nested array of objects

Suppose that I have the following three collections:
product: [
{
_id: ObjectId('p1'),
name: 'Box1'
}
]
// ----------
order: [
{
_id: ObjectId('o1'),
productId: ObjectId('p1'),
quantity: 10
},
{
_id: ObjectId('o2'),
productId: ObjectId('p1'),
quantity: 20
}
]
// ----------
status: [
{
_id: ObjectId('s1'),
orderId: ObjectId('o1'),
title: 'in-progress'
},
{
_id: ObjectId('s2'),
orderId: ObjectId('o2'),
title: 'succeeded'
}
]
I need to join these three to get following result:
{
_id: ObjectId('p1'),
name: 'Box1',
order: [
{
_id: ObjectId('o1'),
quantity: 10,
status: {
_id: ObjectId('s1'),
title: 'in-progress'
}
},
{
_id: ObjectId('o2'),
quantity: 20
status: {
_id: ObjectId('s2'),
title: 'succeeded'
}
}
]
}
Actually the problem is inside the order array which has 2 objects in it to correlate with the relevant status collection.
Here what I did something like:
db.getCollection('product').aggregate([
{
$lookup: {
from: 'order',
localField: 'productId',
foreignField: '_id',
as: 'product.order'
},
},
{
$lookup: {
??? // Make status inside each element of order array
},
},
]);
Does anyboady have idea?
You will need to $unwind the result of the first lookup so that you can consider each separately, do a $lookup from the status collection on orderId, and then $group by the product _id and $push the orders back into an array.
The answer:
db.getCollection('product').aggregate([{
$lookup: {
from: 'order',
localField: '_id',
foreignField: 'productId',
as: 'orders'
}
},
{
$unwind: '$orders'
},
{
$lookup: {
from: 'status',
localField: 'orders._id',
foreignField: 'orderId',
as: 'orders.status'
}
},
{
$unwind: '$orders.status'
},
{
$group: {
_id: '$_id',
name: {
$first: '$name'
},
order: {
$push: '$orders'
}
}
}
]);
Then result would be something like this:
[{
_id: ObjectId('p1'),
name: 'Box1',
order: [{
_id: ObjectId('o1'),
productId: ObjectId('p1'),
quantity: 10,
status: {
_id: ObjectId('s1'),
orderId: ObjectId('o1'),
title: 'in-progress'
}
},
{
_id: ObjectId('o2'),
productId: ObjectId('p1'),
quantity: 20,
status: {
_id: ObjectId('s2'),
orderId: ObjectId('o2'),
title: 'succeeded'
}
}
]
}]

Implementing joins on multiple collections

I am trying to create a social networking application which can have connect (followers, following), posts, comments, likes, shares, etc. This is a MVP project, but still i wanted to explore mongoDB for this use case. I am having some doubt regarding the performance of this application.
I have three collection:
Posts: This is where a new post shall be added. This collection contains all the details related to a post.
Schema:
const postSchema = new mongoose.Schema({
user_id: String,
title: String,
text: String,
type: { type: String, enum: ["music", "movie", "tv"] },
mediaUrl: String,
thumbnailUrl: String,
accessType: { type: Number, enum: [1, 2, 3] },
tags: [String],
like_count: Number,
comment_count: Number,
share_count: Number,
insertDate: {
type: Date,
default: () => {
return new Date();
}
}
});
Feeds: This collection just add a metadata of user and post including tags. This i intend to use to get the relevant feed for a particular user.
Schema:
const feedSchema = new mongoose.Schema({
post_id: String,
user_id: String,
isTag: Boolean,
isPublic: Boolean,
insertDate: {
type: Date,
default: () => {
return new Date();
}
},
modifiedDate: {
type: Date,
default: () => {
return new Date();
}
}
});
Connects: This collection is for the relationship of users.
Schema:
const connectSchema = new mongoose.Schema({
followed_by: String,
user_id: String,
insertDate: {
type: Date,
default: () => {
return new Date();
}
}
});
My approach was to first find the posts from feeds collection basis users whom I am following, then fetching the posts from post collection.
Here goes my attempted query:
db.connects.aggregate([
{ $match: { followed_by: "5cbefd61d3b53a4aaa9a2b16" } },
{
$lookup: {
from: "feeds",
let: { user_id: "$user_id" },
pipeline: [{ $match: { $expr: { $or: [{ $eq: ["$user_id", "$$user_id"] }, { isPublic: true }] } } }],
as: "feedData"
}
},
{ $unwind: "$feedData" },
{ $replaceRoot: { newRoot: "$feedData" } },
{ $group: { _id: { post_id: { $toObjectId: "$post_id" }, modifiedDate: { $toLong: "$modifiedDate" } } } },
{ $replaceRoot: { newRoot: "$_id" } },
{ $sort: { modifiedDate: -1 } },
{ $skip: 0 },
{ $limit: 10 },
{
$lookup: { from: "posts", localField: "post_id", foreignField: "_id", as: "postData" }
},
{ $unwind: "$postData" },
{ $replaceRoot: { newRoot: "$postData" } },
{ $addFields: { userId: { $toObjectId: "$user_id" } } },
{
$lookup: { from: "users", localField: "userId", foreignField: "_id", as: "userData" }
},
{
$project: {
post_id: "$_id",
user_id: 1,
title: 1,
text: 1,
typeaccessType: 1,
mediaUrl: 1,
thumbnailUrl: 1,
insertDate: { $toLong: "$insertDate" },
like_count: 1,
comment_count: 1,
share_count: 1,
user_email: { $arrayElemAt: ["$userData.email", 0] },
user_profile_pic: { $arrayElemAt: ["$userData.profile_pic", 0] },
username: { $arrayElemAt: ["$userData.firstname", 0] }
}
}
]).pretty();
Please share your feedback on:
Which index should I use to boost up the performance?
Is there is a better way of doing the same in mongoDB. Also if any part of the query can be optimised?

Resources