MongoDB/Mongoose Query for multiple objects - node.js

I got an MongoDB database that consists from 3 collections.
Categories
Subcategories
Products
Each product has the following model:
const ProductSchema = new Schema({
category: String,
subcategory: String,
name: String,
description: String,
price: String,
Image: [{
url: String,
filename: String
}],
deleteImages: []
});
What I want is to query a category then get the subcategories that belong to the category and (here is the question:) from the found subcategories query the products that belong to them.
app.get("/api/front/show/:category", asyncHandler(async(req,res)=>{
const category = req.params.category;
const subcategories = await SubCategoryMd.find({'category' : category});
const products = await ProductMd.find({/* Pass here the found subcategories */});
res.json({subcategories, products});
}));
How do I query multiple objects with find?

app.get("/api/front/show/:category", asyncHandler(async(req,res)=>{
let products = []
const category = req.params.category;
const subcategories = await SubCategoryMd.find({'category' : category});
const productsBulk = await ProductMd.find({}).collation({ locale: 'el' }).sort('name');
productsBulk.forEach(product => {
subcategories.forEach(subcategory => {
if(subcategory.name === product.subcategory){
products.push(product)
}
});
});
res.json({subcategories, products});
}));
This is a simple solution but it already pulls all the products and on top of that it is a very expensive function to run in my opinion.

Related

Can't query a record in MongoDB when using "$" selector

I have a weird problem when using NodeJS with Mongoose when querying a record in MongoDB.
I have a Product model file is like this:
const mongoose = require("mongoose");
const productSchema = new mongoose.Schema({
name: String,
brand: String,
shorDescription: String,
description: String,
price: Number,
salePercent: Number,
rating: Number,
color: String,
size: [String],
});
const Product = mongoose.model("Product", productSchema);
module.exports = Product;
And in the controller file, my code is like this:
const Product = require("../models/product.model");
exports.getProducts = async (req, res) => {
const products = await Product.find({ rating: { $lte: "5" } });
console.log(`products: ${products}`);
res.json({
products: products,
});
};
Product.find({}) works perfectly -> returns all the records in the product collection
Product.find({brand: "Nike"}) still works fine -> returns all the "Nike" products.
the weird thing is when I use $ selector in Product.find({ rating: { $lte: "5" } }), I receive nothing while I have several products which are match the condition in the product collection.
Someone help me :(((
MongoDB query operators are type sensitive. The problem here is that the query expression is a string ("5"), but the data is a number (e.g. 5). Since the types are incomparable, they will never match the $lte expression.
For instance, the query for {rating: {$lte: "5"}} will match documents like:
{rating: "5"}
{rating: "4.5"}
{rating: "25"}
{rating: ""}
but not like:
{rating: 5}
{rating: 4.5}

Populate array inside object in mongoose

I have a company model which looks like this:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const CompanySchema = new Schema(
{
companyName: String,
settings: {
type: {
priceVisible: Boolean,
allowPickupAddressAddition: Boolean,
paymentMethodsAvailable: [
{ type: Schema.Types.ObjectId, ref: "PaymentMethod" },
],
},
},
}
);
const Company = mongoose.model("Company", CompanySchema);
module.exports = Company;
And I want to populate the values store in paymentMethodsAvailable array. Here is relevant controller code:
const company = await Company.findOne({ _id: id }).populate([
{
path: "settings",
populate: [{path: "paymentMethodsAvailable"}]
},
]);
But this doesn't work as expected. I can see that it might be trying to populate settings object, and fails there. Is there a way in which I can tell mongoose to populate settings.paymentMethodsAvailable ?
Try this
const company = await Company.findOne({ _id: id }).populate(
"settings.paymentMethodsAvailable"
);
You can find more examples in the documentation. I was using this section as a reference https://mongoosejs.com/docs/populate.html#populating-maps
Mongoose provides clear syntax
The following code will work fine
const company = await Company.findOne({ _id: id }).populate(
"settings.type.paymentMethodsAvailable"
);
if(!company) {
// if there is no company with that id
// this acully not error it's simply
// return `null` to tell you company not found.
res.status(404).send()
}
Also: you can go further and populate specific fields inside settings.paymentMethodsAvailable
const company = await Company.findOne({ _id: id }).populate(
"settings.type.paymentMethodsAvailable",
"field1 filed2 filed3"
);

How to add ID of one mongoose collection to an array in another collection?

I'm trying to add the ID of my category documents to my budget documents. Below is the Schema for my budgets.
var {mongoose} = require('../db/mongoose');
var budgetsSchema = new mongoose.Schema({
year: Number,
categoryIDs: [{type:mongoose.Schema.ObjectId,
ref: 'categories'}]
});
var Budgets = mongoose.model('Budgets', budgetsSchema);
module.exports = {
Budgets
};
And here is the Schema for my categories collection.
var {mongoose} = require('../db/mongoose');
var categorySchema = mongoose.Schema({
name: String,
amount: Number,
sub_categories: [{
name: String,
amount: Number
}]
})
var categories = mongoose.model('categories', categorySchema);
module.exports = {
categories
};
To post categories, I use this express post request to add the categories to the category collection and its ID to the Budget collection.
//The ID is the Budget ID
app.post('/categories/:id', (req, res) => {
var id = req.params.id;
var sub_categories = req.body.sub_categories;
var category = new categories({
name: req.body.name,
amount: req.body.amount,
sub_categories
})
category.save().then((docs) => {
res.send(docs);
console.log(docs)
}).catch((e) => res.status(404).send(e));
Budgets.findById(id).then((docs) => {
if(!docs) {
res.status(404).send();
}
docs.categoryIDs.push(category._id);
}).catch((e) => {
res.send(e).status(404);
})
})
When I run this, it does add the category to the collection, but it does not add the ID to the categoryIDs array in the Budget document. Please help
First, change the model name from Plural to Singular as mentioned in the mongoose docs to avoid confusion:
The first argument is the singular name of the collection your model
is for. Mongoose automatically looks for the plural version of your
model name. Thus, for the example above, the model Tank is for the
tanks collection in the database. The .model() function makes a copy
of schema. Make sure that you've added everything you want to schema
before calling .model()!
So categories to Category and Budgets to Budget. Please verify the new before mongoose.model here, Mongoose docs ref.
var categorySchema = new mongoose.Schema({
name: String,
amount: Number,
sub_categories: [{
name: String,
amount: Number
}]
})

Mongoose: Exclude objects from results based on another model

Let's say I have a User and Group model, groups have users, like
var GroupSchema = mongoose.Schema({
name: String,
users: [{ type: mongoose.Schema.ObjectId, ref: 'User' }]
});
How would I query to get all the Users but exclude the ones that are on
Group.users, I'm already doing this by querying first Group then manually filtering against all users
var groupP = Group.findById(group_id).populate('users');
var userP = User.find();
Promise.props({
group: groupPromise.exec(),
users: usersPromise.exec()
})
.then(function (result) {
//this gives the expected result but I'm looking for a more straight forward mongoose only solution if possible
var users = differenceWith(result.users, result.group.users, (a, b) => { return a._id.toString() == b._id.toString()});
})
You can try below query.
var groupP = Group.findById(group_id);
var userP = User.find({_id:{$nin:groupP.users}});

How to find count from two collections relationship in MongoDB using Mongoose

I want to display list files of particular user (_id: 876896) with their click counts as below:
Sr. No. | File Name | Click Count
Below is the sample schema I am using:
var clicks = mongoose.Schema({
file_id : String,
IP : String
});
var files = mongoose.Schema({
filename : String,
owner : {type:mongoose.Schema.ObjectId, ref:'users'},
});
How effectively this can done.
You can do it in two step, first get all files that refer to the users you want data for. Then, get alls clicks that are related to files you read.
There is no INNER_JOIN in mongodb
Little example:
files.find({
owner: {
$in: usersIdsArray // [_id, _id...] ids of all users
},
}).then((ret = []) => {
// Here ret is array of files
if (!ret.length) // There is no files matching the user
return clicks.find({
file_id: {
$in: Array.from(ret, x => x._id.toString()),
// build an array like [_id, _id, _id...]
},
});
}).then((ret = []) => {
// Here you have all clicks items
});
I also recommend to use embedded schema instead of multiple collections:
var clicks = mongoose.Schema({
file_id: String,
IP: String
});
var files = mongoose.Schema({
filename: String,
owner: {type:mongoose.Schema.ObjectId, ref:'users'},
});
turn into:
var files = mongoose.Schema({
filename: String,
owner: {type:mongoose.Schema.ObjectId, ref:'users'},
clicks: [String], // With String your IP
});
MongoDB is good when it comes to big data, but not that good in relations.

Resources