Problem in Mongoose $lookup for nested array of objects - node.js

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'
}
}
]
}]

Related

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

Mongodb $lookup/populate and summarize

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

Group in group using Mongoose

I have array of objects:
[
{type: 'type1', user: 'user1', order: 'order-10', price: 100},
{type: 'type1', user: 'user1', order: 'order-20', price: 200},
{type: 'type2', user: 'user2', order: 'order-30', price: 300},
{type: 'type2', user: 'user1', order: 'order-40', price: 400}
]
Is it possible group it by type and inside type group, group by user, like this:
[
{_id: 'type1', users: [
{user: 'user1', orders: [{order: 'order-10', price: 100}, {order: 'order-20', price: 200}]}
]},
{_id: 'type2', users: [
{user: 'user1', orders: [{order: 'order-40', price: 400}]},
{user: 'user2', orders: [{order: 'order-30', price: 300}]}
]}
]
I am trying to do as follows but so is not grouped by user:
db.aggregate([
{ $match: { date: { $gte: new Date(req.body.date.begin), $lt: end } } },
{ $lookup: { from: 'orders', localField: 'order', foreignField: '_id', as: 'order' } },
{ $unwind: "$order" },
{ $lookup: { from: 'clients', localField: 'user', foreignField: '_id', as: 'user' } },
{ $unwind: "$user" },
{
$group: {
_id: {
type: '$type',
user: '$user.name',
order: '$order'
},
orders: {
$push: { order: '$order', price: '$price' }
}
}
}, {
$group: {
_id: '$_id.type',
users: { $push: { user: '$_id.user', orders: '$orders' } }
}
}
]
db.aggregate([{
"$unwind: "$arrayOfObject"
},{"$group":{
"_id":"$type",
"users":{
"$addToSet":"$user",
},
"orders":{
"$addToSet":"$orders"
},
"price":{
"$addToSet":"$price"
}
}}])

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?

Join documents in array mongodb

I have collections of characters here like this.
characters collection
{
_id: objectId(1), //this is just an example id
name: 'harry'
},
{
_id: objectId(2),
name: 'ron'
},
{
_id: objectId(3),
name: 'dumbledor'
},
{
_id: objectId(4),
name: 'Super Man'
}
then i have another one
movie collection
{
_id: objectId(1),
heroes_id: [
_id: objectId(1),
_id: objectId(2),
_id: objectId(3)
],
movie: 'Harry Potter'
}
I wanted it to join them like this
{
_id: objectId(1),
heroes_id: [
_id: objectId(1),
_id: objectId(2),
_id: objectId(3)
],
movie: 'Harry Potter',
cast: [
{
{
_id: objectId(1), //this is just an example id
name: 'harry'
},
{
_id: objectId(2),
name: 'ron'
},
{
_id: objectId(3),
name: 'dumbledor'
},
}
]
}
i have a piece of code with mongodb functions
movie.aggregate([
{
$unwind: "$heroes_id"
},
{
$lookup: {
from: "characters",
localField: "heroes_id",
foreignField: "_id",
as: "cast"
}
},
{
$match: { "cast": { $ne: [] } }
}
]).exec(function(err, results) {
if(err) res.status(500).send(err);
res.status(200).send(results);
});
so i just wanted to join the two collections. it work but it get only one. please help me out thanks.

Resources