Query nested object mongoose - node.js

i am trying to reproduce the "like" behavior in mongoose by searching all elements where user name is something like "abcd..."
this function return abject that i feed db.find function with
const rgx = (pattern) => new RegExp(`.*${pattern}.*`);
const orFilter = [];
fields.forEach((field) => {
if (field.includes('.')) {
const [parent, child] = field.split('.');
orFilter.push({
[parent]: { [child]: { $regex: rgx(searchText), $options: 'i' } },
});
} else {
orFilter.push({ [field]: { $regex: rgx(searchText), $options: 'i' } });
}
});
return { $or: orFilter };
entry 1: ['city','zipCode'] working ok
entry 2 : ['city','zipCode','user.name'] does not work.
i have this message *CastError: Cast to ObjectId failed for value
{ name: { '$regex': /.abc./, '$options': 'i' } }
*

the solution to my probleme was found by using aggregation visit
https://www.mongodb.com/docs/manual/reference/method/db.collection.aggregate/!
i tried to access a value stored in another table user.name

Related

nested map() and find() do not work in express + mongoose but work in codesandbox

I got a route in express that get 2 different array of object from mongoDb and then return a new "contributions" array after i've added some data into it from "projectAll"
Here is one contributions object:
{
_id: "5f5b095f01ba8e40769f7301",
libId: "5f5a7a7701ba8e40769f72fb",
totalPaidAmount: 10000,
transactionId: "pi_1HQ4hVGmJhXXrXOXnr0pkXkv",
cart: [
{
_id: "5f5b095f01ba8e40769f7302",
amount: 5000,
projectId: "5f5b086601ba8e40769f72fe"
},
{
_id: "5f5b095f01ba8e40769f7303",
amount: 5000,
projectId: "5f5b08ae01ba8e40769f7300"
}
],
__v: 0
}
And one projectAll object:
{
projectCover: { id: "211290" },
title: "My title 2",
funded: 11000,
description: "Desc",
_id: "5f5b08ae01ba8e40769f7300",
libId: "5f5a7a7701ba8e40769f72fb",
__v: 0
}
I need to add projectAll.title and projectAll.projectCover into each contributions.cart objects.
To do so I match contribution.cart.projectId with projectAll._id.
router.get("/contributions/:id", async (req, res) => {
const id = req.params.id
try {
const contributions = await Contribution.find({id})
const projectAll = await Project.find({id})
const updatedContribution = contributions.map((contribution) => {
// Go through each cart of each contribution
const updatedCart = contribution.cart.map((cartItem) => {
// Find matching project
const matchingProject = projectAll.find((project) => {
// project OK =====> console.log(project)
// projectAll OK =====> console.log(projectAll)
// cartItem OK =====> console.log(cartItem)
return project._id === cartItem.projectId;
});
// Here return undefined =====> console.log(matchingProject)
const {projectCover, title} = matchingProject
return {...cartItem, projectCover, title
}
})
return { ...contribution, cart: updatedCart
}
})
res.send(updatedContribution)
} catch (err) {
res.status(500).send(err)
}
this code work perfectly in my codeSandBox : https://codesandbox.io/s/contribution-map-projects-vhv8z?file=/src/index.js
But in my express + mongoose environment I get undefined for matchingProject (i added comments in the code to show from where I get unwanted result)
Does anybody know why it doesn't work ?
Thanks a lot !
EDIT: console.log(typeof project._id, typeof cartItem.projectId) return object object
whereas in codesandbox those are strings.
Since they are both ObjectIds you can use mongoose equals() functions - so project._id.equals(cartItem.projectId). You cannot compare them, cause you'd compare their object reference. So either the above function will work or project._id.toString() === cartItem.projectId.toString()

How to search with mongooseJs with nested logic when all parameter are not guaranteed?

I want to search in a collection with two parameters and there is no guarantee that both parameters will be available anyone of them can be missing I want to ignore it and search only with one parameter.
I also want to search in two fields with the second parameter using $or.
My Code
NodeJs Express Mongoose
colec.find({
$and: [{
'address.zip': req.query.p,
$or: [{ 'name': req.query.n }, { 'tags': req.query.n }]
}]
}, function (err, foundProfiles) {
//Some Code
})
my code before tags search
var terms = {};
if (req.query.q) {
var name = req.query.q;
}
if (req.query.p) {
terms['address.zip'] = req.query.p;
}
colec.find(terms, function(err, foundProfiles){
//some code
})
I got this after searching for a long time.
var dbQueries = [];
if (req.query.q) {
var search = req.query.q;
dbQueries.push({
$or: [
{ name: search },
{ tags: search },
]
});
}
if (req.query.p) {
dbQueries.push({ 'address.zip': req.query.p });
}
dbQueries = { $and: dbQueries }
Collection.find(dbQueries, function (err, foundProfiles) {
//some code
});

MongoDB - find one and add a new property

Background: Im developing an app that shows analytics for inventory management.
It gets an office EXCEL file uploaded, and as the file uploads the app convert it to an array of JSONs. Then, it comapers each json object with the objects in the DB, change its quantity according to the XLS file, and add a timestamp to the stamps array which contain the changes in qunatity.
For example:
{"_id":"5c3f531baf4fe3182cf4f1f2",
"sku":123456,
"product_name":"Example",
"product_cost":10,
"product_price":60,
"product_quantity":100,
"Warehouse":4,
"stamps":[]
}
after the XLS upload, lets say we sold 10 units, it should look like that:
{"_id":"5c3f531baf4fe3182cf4f1f2",
"sku":123456,
"product_name":"Example",
"product_cost":10,
"product_price":60,
"product_quantity":90,
"Warehouse":4,
"stamps":[{"1548147562": -10}]
}
Right now i cant find the right commands for mongoDB to do it, Im developing in Node.js and Angular, Would love to read some ideas.
for (let i = 0; i < products.length; i++) {
ProductsDatabase.findOneAndUpdate(
{"_id": products[i]['id']},
//CHANGE QUANTITY AND ADD A STAMP
...
}
You would need two operations here. The first will be to get an array of documents from the db that match the ones in the JSON array. From the list you compare the 'product_quantity' keys and if there is a change, create a new array of objects with the product id and change in quantity.
The second operation will be an update which uses this new array with the change in quantity for each matching product.
Armed with this new array of updated product properties, it would be ideal to use a bulk update for this as looping through the list and sending
each update request to the server can be computationally costly.
Consider using the bulkWrite method which is on the model. This accepts an array of write operations and executes each of them of which a typical update operation
for your use case would have the following structure
{ updateOne :
{
"filter" : <document>,
"update" : <document>,
"upsert" : <boolean>,
"collation": <document>,
"arrayFilters": [ <filterdocument1>, ... ]
}
}
So your operations would follow this pattern:
(async () => {
let bulkOperations = []
const ids = products.map(({ id }) => id)
const matchedProducts = await ProductDatabase.find({
'_id': { '$in': ids }
}).lean().exec()
for(let product in products) {
const [matchedProduct, ...rest] = matchedProducts.filter(p => p._id === product.id)
const { _id, product_quantity } = matchedProduct
const changeInQuantity = product.product_quantity - product_quantity
if (changeInQuantity !== 0) {
const stamps = { [(new Date()).getTime()] : changeInQuantity }
bulkOperations.push({
'updateOne': {
'filter': { _id },
'update': {
'$inc': { 'product_quantity': changeInQuantity },
'$push': { stamps }
}
}
})
}
}
const bulkResult = await ProductDatabase.bulkWrite(bulkOperations)
console.log(bulkResult)
})()
You can use mongoose's findOneAndUpdate to update the existing value of a document.
"use strict";
const ids = products.map(x => x._id);
let operations = products.map(xlProductData => {
return ProductsDatabase.find({
_id: {
$in: ids
}
}).then(products => {
return products.map(productData => {
return ProductsDatabase.findOneAndUpdate({
_id: xlProductData.id // or product._id
}, {
sku: xlProductData.sku,
product_name: xlProductData.product_name,
product_cost: xlProductData.product_cost,
product_price: xlProductData.product_price,
Warehouse: xlProductData.Warehouse,
product_quantity: productData.product_quantity - xlProductData.product_quantity,
$push: {
stamps: {
[new Date().getTime()]: -1 * xlProductData.product_quantity
}
},
updated_at: new Date()
}, {
upsert: false,
returnNewDocument: true
});
});
});
});
Promise.all(operations).then(() => {
console.log('All good');
}).catch(err => {
console.log('err ', err);
});

mongoose find() document with two fields with one search parameter

My node model contains following properties
firstName
lastName
age
address
I need to use mongoose find functions to filter user with firstName and lastName.
My UI pass only one search parameter. Data should filter as follows
'fistName' like 'search parameter' & lastName like 'search Parameter'.
I pass following object to find function.It did not work for me.
var criteria = {
'firstName' : req.body.customerName ? { $regex: req.body.customerName, $options: 'i' } : null ,
'lastName' : req.body.customerName ? { $regex: req.body.customerName, $options: 'i' } : null
};
If you want to get data if match with both fields of firstName and lastName then you can use $and operator with $regex.
var query = {$and:[{firstName:{$regex: req.body.customerName, $options: 'i'}},{lastName:{$regex: req.body.customerName, $options: 'i'}}]}
and If you want to get data if match with any one field of firstName and lastName then you can use $or operator with $regex.
var query = {$or:[{firstName:{$regex: req.body.customerName, $options: 'i'}},{lastName:{$regex: req.body.customerName, $options: 'i'}}]}
so can try this code:
var query = {}
if(req.body.customerName) {
query = {$or:[{firstName:{$regex: req.body.customerName, $options: 'i'}},{lastName:{$regex: req.body.customerName, $options: 'i'}}]}
}
ModelName.find(query , function (err, data) {
if(error) {
// return error
}
//return data
});
So, I think you have a logical flaw in addition to a syntactic flaw. Unless you're intentionally looking only for people who have the same first name and last name (e.g. Tom Tom), then you'll never find anyone by simply finding on both fields with the same value in each. If, as I suspect, you really want to take a search criteria and see if a user has that string in either their first name or last name, then you'll actually want to use $or.
Something like:
let query = {};
if(req.body.customerName){
const nameExp = new RegExp('^'+req.body.customerName+'$', 'i');
query = { $or : [ { firstName: nameExp }, { lastName: nameExp } ] };
}
MyModel.find(query, (err, data) => { /* do your thing */ });
var query = {}
if(req.body.customerName) {
query = {
firstName :new RegExp('^'+req.body.customerName+'$', "i"),
lastName : new RegExp('^'+req.body.customerName+'$', "i")
}
}
MyModel.find(query , function (err, data) {
// data.forEach
});
You can try pass the following criteria
var criteria = {
'firstName' : req.body.customerName ? {$regex: req.body.customerName + '.*', $options: 'i'} : null ,
'lastName' : req.body.customerName ? {$regex: req.body.customerName + '.*', $options: 'i'} : null
};

dynamically add fields to find() mongodb

Hi i am working with mongoDB , i am trying to crate a dynamic object and pass as an argument to the find()
my object is like this
Library
var search = {};
if(data.orderId) {
search["_id"] = { $in: data.orderId.split(",") } ;
}if(data.path) {
search["stops.districtId"] = data.path;
}if(data.special) {
search["special.option"] = { $in: data.special.split(",") } ;
}if(data.userInfo) {
search["UserId"] = data.userInfo;
}
then i will pass my search objet to the query like this
Model
var col = db.collection( CustomerOrderModel.collection() );
col.find(
{
serviceType:data.serviceType,
**search**
}
).skip(data.skip).limit(data.limit).sort(data.sort).toArray(function(err, res) {
if (err) {
reject( err );
} else {
resolve( res );
}
});
the problem here is when i console.log my search object i can see
'special.option': { '$in': [ 'ROUND_TRIP' ] } }
my $in is wrapped with quotes . so my query doesn't work .
if i directly type "special.option": { $in: [ 'ROUND_TRIP' ] } } in my query it is working correctly .
i m trying to built this search object because i have multiple fields to search with complex logic . i don't want to do these in my
model , so i wil create the search object in my library .
is there any possible ways to this , thanks in advance .
You should make the search object part of the query by adding the extra filters into the search object. As you are currently doing
col.find({
serviceType:data.serviceType,
search
})
this is interprated as
col.find({
serviceType:data.serviceType,
{ 'special.option': { '$in': [ 'ROUND_TRIP' ] } }
})
You should be able to add the serviceType filter to your existing search object using the square bracket notation as follows:
search["serviceType"] = data.serviceType;
then you can pass that object in your query:
var col = db.collection( CustomerOrderModel.collection() );
search["serviceType"] = data.serviceType;
col.find(search)
.skip(data.skip)
.limit(data.limit)
.sort(data.sort)
.toArray(function(err, res) {
if (err) { reject( err ); }
else { resolve( res ); }
});
That is not the problem.
console.log({ "special.option": { $in: [ 'ROUND_TRIP' ] } });
gives
{ 'special.option': { '$in': [ 'ROUND_TRIP' ] } }
so this is correct.
In your code you just write **search** in the most critical part, but try this:
search["serviceType"] = data.serviceType;
col.find( search )

Resources