I'm new to mongoose and MongoDB and I have a question,
I want to find documents that contain a reference of reference.
I have 2 models like this:
1: Posts with ref to categories:
const postsSchema = new Schema({
post_title: {
type: String,
required:true
},
post_categories: [{
type: Schema.Types.ObjectId,
ref: 'categories',
required:true
}],
});
2: categories with ref to categories
const categoriesSchema = new Schema({
categoryName: {
type: String,
required: true
},
categoryParent: {
type: Schema.Types.ObjectId,
ref: 'categoriesSchema',
}
});
I want to find all posts which have a category parent for example (news), I try this:
Posts.find({'post_categories.categoryParent.categoryName': 'news'});
but I got an empty array [].
Is there a way to find documents that contain a reference of reference?
I found the solution by using MongoDB aggregate, I don't know if it optimal solution, but it solved my issue:
var allnews = await postsDB.aggregate([
{$unwind: "$post_categories"},
{$lookup: {
from: "categories",
localField: "post_categories",
foreignField: "_id",
as: "newCategoryName"
}},
{ $unwind: "$newCategoryName"
},
{$lookup: {
from: "categories",
localField: "newCategoryName.categoryParent",
foreignField: "_id",
as: "newCategoryParent"
}},
{ $unwind: "$newCategoryParent"
},
{
$match: {
"newCategoryParent.categoryName": "News"
}}
]);
Now I have all posts which have the parent category is "News" whatever child category is
Related
I'm trying to find all the docs from groupUserRoleSchema with a specific $match condition in the child. I'm getting the expected result, but the child application inside groupSchema is coming as an array.
I just need the first element from the application array as an object. How to convert this application into a single object.
These are my models
const groupUserRoleSchema = new mongoose.Schema({
group: {
type: mongoose.Schema.Types.ObjectId,
ref: 'group'
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
}
});
const groupSchema = new mongoose.Schema({
application: {
type: mongoose.Schema.Types.ObjectId,
ref: 'application'
}
});
Here is my aggregate condition.
groupUserRoleModel.aggregate([
{
$lookup: {
from: "groups", //must be PHYSICAL collection name
localField: "group",
foreignField: "_id",
as: "group",
}
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "user",
}
},
{
$lookup: {
from: "applications",
localField: "group.application",
foreignField: "_id",
as: "group.application",
}
},
{
$addFields: {
group: {
$arrayElemAt: ["$group", 0],
},
user: {
$arrayElemAt: ["$user", 0],
}
},
},
{
$match: {
"user.email_id": requestHeaders.user_email
},
}
]);
Here the $lookup group.application is coming as an array. Instead i need it as an object.
Below added is the current output screen-shot
Here is the expected output screen-shot
Any suggestions ?
A alternative is re-writing the main object return, and merge the fields.
The lookup field, gonna use the $arrayElemAt attribute at position 0, with the combination of the root element '$$ROOT'.
const result = await groupUserRoleModel.aggregate([
{
$match: {
"user.email_id": requestHeaders.user_email //The Match Query
},
},
{
$lookup: {
from: 'groups', // The collection name
localField: 'group',
foreignField: '_id',
as: 'group', // Gonna be a group
},
},
{
// Lookup returns a array, so get the first once it is a _id search
$replaceRoot: {
newRoot: {
$mergeObjects: [ // merge the object
'$$ROOT',// Get base object
{
store: {
$arrayElemAt: ['$group', 0], //Get first elemnt
},
},
],
},
},
},
]);
I have already looked up some questions related to this but with no luck at all. I have the following schemas
const Card= new mongoose.Schema(
{
name: { type: String },
collectionId: { type: mongoose.Schema.Types.ObjectId, ref: "Collection" },
price: { type: Number}
},
{ timestamps: true }
);
const Collection= new mongoose.Schema(
{
name: { type: String },
},
{ timestamps: true }
);
const Transactions = new mongoose.Schema(
{
amount: { type: Number},
cardId: { type: mongoose.Schema.Types.ObjectId, ref: "Card" },
collectionId: {type: mongoose.Schema.Types.ObjectId, ref: "Collection"},
userId: {type: mongoose.Schema.Types.ObjectId, ref: "User" }
},
{ timestamps: true }
);
lets say I want to aggregate transactions to get the user who paid most to buy cards and his info then I will do something like this
const T= require("transaction model")
const User = require("user model")
const res = await T.aggregate([
{
$group: {
_id: "$userId",
totalPaid: { $sum: "$amount" },
cardsBought: { $sum: 1 },
},
},
{
$lookup: {
from: "users" / User.document.name
localField: "userId",
foreignField: "_id",
as: "userInfo",
},
},
])
I followed similar questions answers and made sure that the collection name is correct in the "from" field (I tried both users and User.document.name) and made sure the localField and foreignField types are the same(they are both mongo ObjectId) but still I get userInfo as empty array and I am not really sure why.
A work around that I have tried is making two queries like the following
const res = await T.aggregate([])
await User.populate(res, {path: "_id", select: {...}})
but the issue is that I can't do multiple lookups (lets say I want to populate some other data as well)
I have 3 collections, posts, comments and users, the schemas for posts and comments are below:
post schema
const PostSchema: Schema = new Schema({
author: { type: String, ref: 'User' },
postID: {type: String},
text: { type: String },
});
comment schema
const commentSchema: Schema = new Schema({
author: { type: Schema.Types.ObjectId, ref: 'User' },
parentPost: {type: Schema.Types.ObjectId, ref: 'Post'},
parentPostID: {type: String}, // Post that was commented on
});
To get the comment from each post, I do an aggregate query:
const aggregateQuery = [
{
$match:{postID:{ $in: postKeys},},
},
{
$lookup:
{
from: 'comments',
localField: '_id',
foreignField: 'parentPost',
as: 'Top_Comment',
},
},
{
$sort: {
createdAt: 1,
}
,
},
]
That query works, but I can't figure out how to populate the author property of the comment since its a ref to a user.
What I tried
I tried using pipeline, but I'm a new to aggregation so I didn't know how, any ideas and solutions would be really appreciated
I have the following schemas (Product, ProductCategory):
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
productCategory: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ProductCategory'
}
})
const productCategorySchema = new mongoose.Schema({
name: {
type: String,
required: true
}
})
What I would like to do is to query all the Product documents who has a certain Product.productCategory.name = ?
I read about population in mongodb but can't really know how to apply it here.
You could use aggregation function '$lookup'
db.productCategory.aggregate([{
$match: {
name: "{category_name}"
}
}, {
$lookup: {
from: 'product',
localField: '_id', // ProductCategory._id
foreignField: 'type', // Product.product_category_id
as: 'products'
}
}]);
I have a Mongoose model which have paths which need to be populated :
var adsSchema = new Schema({
price: { type: Number, min: 0, required: true },
author: { type: ObjectId, ref: 'Users', required: true },
title: { type: String, required: true },
date: { type: Date, default: Date.now },
offers: [{
price: { type: Number, required: true },
date: { type: Date, default: Date.now },
offerBy: { type: ObjectId, ref: 'Users', required: true }
}],
category: { type: ObjectId },
images: [{
type: ObjectId,
ref: 'Images'
}],
videos: [String]
});
In some GET request, I need to populated numerous fields as I was saying, in particular offers with a sorting by 'price' by ascending.
As the documentation of Mongoose is showing there (http://mongoosejs.com/docs/api.html#query_Query-populate), you can sort by a subpath.
My problem is that it isn't the case.
There is my code which is doing it :
Ads.findOne({ _id: adId })
.populate({ path: 'author', select: 'firstName lastName' })
.populate('images')
.populate({ path: 'offers', options: { sort: { 'price': -1 } })
.populate({ path: 'category', select: 'name' })
.exec(function (err, ad) {
if (err)
deferred.reject({ status: 500, message: 'Database error.' });
else if (!ad)
deferred.reject({ status: 500, message: 'The ad doesn\'t exist!.' });
else {
deferred.resolve(ad.toJSON());
}
})
I have read as much as possible questions/answers giving here or in the Mongoose mailing-list :
https://stackoverflow.com/a/19450541/6247732
I know it's not possible to sort documents results from subdocument result, OK. But I just want to sort that subdocument only, and only this one. Some responses here seems to say it's possible :
https://stackoverflow.com/a/31757600/6247732
https://stackoverflow.com/a/16353424/6247732
I have 2 questions around it :
Is it possible (as the Mongoose documentation is written) to sort a subdocument during the population ?
Is it linked to the fact I didn't just put a ObjectId linked to an other Schema ?
Thanks for your answer ;)
Personally, I do not use 'populate' as possible as I can. I had experienced many trouble in the past. So, I do not know how to sort while populating.
Instead of using populate, you can use $lookup method on aggregation and you can easily sort any field. Its almost same way with 'population'.
Ads.aggregate([
{
$lookup: {
from: 'Users',
localField: 'author',
foreignField: '_id',
as: 'authorName'
}
},
{
$lookup: {
from: 'Images',
localField: 'images',
foreignField: '_id',
as: 'image'
}
},
{
$lookup: {
from: 'categories',
localField: 'category',
foreignField: '_id',
as: 'category'
}
},
{
$project: {
'authorName.firstName': 1,
'image.imagePath': 1,
'category.categoryName': 1,
'_id': 0
}
},
{
$sort : { 'authorName.firstName' : -1}
}
]).exec(function(err, adss) {
});
I did not check all fields properly. Please implement this way to your model and hope this can give you some idea. Good luck
I found the solution, and it was the response of my second question.
When you are writing a subdocument in your schema like in my question, you are not creating a "relation" between one Model and an other one. Which means that the populate method doesn't work at all.
You have to make a reference to an ObjectId and the model associated to be able to use populate, and so the options which offers you Mongoose. This is an example :
var adsSchema = new Schema({
price: { type: Number, min: 0, required: true },
author: { type: ObjectId, ref: 'Users', required: true },
offers: [{
type: ObjectId,
ref: 'Offers'
}],
videos: [String]
});
Now, the .populate method is working, because Mongoose (and so MongoDB) can perform the subquery on an other Collection and sort it. It wasn't the case before because it was in the same Model, so Mongoose doesn't perform a subquery on the same Collection (as I seems to understand it).