Mongoose & MongoDB: Retrieve results narrowed by multiple parameters - node.js

I need to get data from MongoDB that is first narrowed by one initial category, say '{clothing : pants}' and then a subsequent search for pants of a specific size, using an array like sizes = ['s','lg','6', '12'].
I need to return all of the results where 'pants' matches those 'sizes'.
I've started a search with:
Product.find({$and:[{categories:req.body.category, size:{$in:req.body.sizes}}]},
function(err, products) {
if (err) { console.log(err); }
return res.send(products)
});
I really don't know where to go from there. I've been all over the Mongoose docs.
Some direction would be very helpful.

The mongoose queries can receive object like Mongodb would. So you can pass the search parameters separated by ,
Product.find({categories:req.body.category, size:{$in:['s','lg','6', '12']}})
For more information on $in, check here
For more information on $and operator, check here (note we can ommit the $and operator in some cases and that is what I did)

Related

How to return all matches from a list in MongoDB via Node.js application? [duplicate]

I have a process that returns a list of String MongoDB ids,
[512d5793abb900bf3e20d012, 512d5793abb900bf3e20d011]
And I want to fire a single query to Mongo and get the matching documents back in the same order as the list.
What is the shell notation to do this?
After converting the strings into ObjectIds, you can use the $in operator to get the docs in the list. There isn't any query notation to get the docs back in the order of your list, but see here for some ways to handle that.
var ids = ['512d5793abb900bf3e20d012', '512d5793abb900bf3e20d011'];
var obj_ids = ids.map(function(id) { return ObjectId(id); });
db.test.find({_id: {$in: obj_ids}});
This works fine for me in Robo 3T. No need to create any object and just use the list of ids.
db.getCollection('my_collection').find({'_id':{$in:['aa37ba96']}})
// categoryId comma separated "5c875c27d131b755d7abed86,5c875b0ad131b755d7abed81" in request
var ids= req.body.categoryId.split(',');
db.test.find({ categoryId: { $in: ids } });
If your final purpose is to get the document with the order by your pre-get ids list, you can just convert the query result into mapping(id as key, doc as value) , and then traverse the ids list to get the doc.

Loopback query which compares field values

Say i have the following Scheme
Product: {
Quantity: Number,
SelledQuantity: Number
}
Would it be possible to write a query where all the results returned are where Quantity=SelledQuantity?
If so, is there a way to use it when doing a populate? (Perhaps inside the match field in the opts object ?)
I use mysql connector.
yes as I understood your problem you can do this by following rest call.
http://localhost:3000/api/products?filter[where][SelledQuantity]=n
this will give you the desired results.
This question is more related to MySQL query. But you can achieve it by javascript as follows:
Product.find({}, fuction(err, products) {
if(err) throw err;
//considering products as array of product. Otherwise you can get to depth for array of product.
var filteredProducts = products.filter(function(p1) {
return p1.Quantity === p1.SelledQuantity;
});
//Your desired output
console.log(filteredProducts);
});
This will be slow but will work for smaller database size. For more optimized answer, ask the question in mysql section with respect to database and table structure.

need guidance on node.js multilingual presentation

I am new to node (v0.10) stack.
I am trying to achieve the following:
I have (hopefully) multilingual articles in the latest MongoDB such as:
_id
...more fields...
text: [
{lang: 'en', title: 'some title', body: 'body', slug: 'slug'},
....
]
Everytime I display an article in specific language I query as follows:
var query = Model.findOne({'text.slug': slug});
query.exec(function(err, doc){
async.each(doc.text, function(item, callback){
if (item.lang == articleLang) {
//populate the article to display
}
});
res.render('view', {post:articleToDisplay});
});
Slug is unique for each language!
The problem I have is that mongo will return the whole doc with all subdocs and not just the subdoc I searched for. Now I have to choose to iterate over all subdocs and display the appropriate one on client side or use async.each on the server to get the subdoc I need and only send to the views that one. I am doing it with async on the server. Is that OK? Also async iterates asynchronously but node still waits for the whole loop to finish and then renders the view. Am I missing anything thinking that the user is actually blocked until the async.each finishes? I am still trying to wrap my head around this asynchronous execution. Is there a way I can possibly improve how I manage this code? It seems to be quite standard procedure with subdocs!
Thanks in advance for all your help.
To achieve what you want, you need to make use of the aggregation pipeline. Using a simple findOne() would not be of help,
since you would then have to redact sub documents in your code rather than allowing mongodb to do it. find() and findOne() return the whole document when
a document matches the search criteria.
In the aggregation pipleline you could use the $unwind and $match operators to achieve this.
$unwind:
Deconstructs an array field from the input documents to output a
document for each element. Each output document is the input document
with the value of the array field replaced by the element.
First unwind the document based on the text values array.
$match:
Filters the documents to pass only the documents that match the
specified condition(s) to the next pipeline stage.
Then use the $match operator to match the appropriate documents.
db.Model.aggregate([
{$unwind:"$text"},
{$match:{"text.slug":slug,"text.lang":articleLang}}
])
Doing this would return you only one document with its text field containing only one object. such as: (Note that the text field in the output is not an array)
{ "_id" : ... ,.., "text" : { "slug" : "slug", "lang" : "en" ,...} }

Query MongoDB for documents with non-empty intersection of arrays

So - I have a Heroku + Node + Express + MongoDB app in development, and I've been playing around all day trying to figure out how to get the mongo query I want. In a mongo collection, every document has a property 'fruits', which is an array of strings like
fruits : ['apple', 'orange', 'lemon']
The size and contents of fruits is arbitrary. I want to query the database for all documents whose fruits array has at least one element in common with another array which I provide when I make the query. How can I achieve this?
What I've tried so far centers around the $where query and some server-side JS. If I do something like
mongo.Db.connect(mongoUri, function(err, db) {
db.collection('Users', function(er, collection) {
collection.find( {$where: 'false' } ).toArray(function(err, matches){
console.log(matches)
...
});
});
});
I get an empty array - good! And if I change the false to true, I get the whole DB - also good! Things get less good with something like the following:
collection.find( {$where: function(){return false} } ).toArray(function(err, matches){...
I get the whole DB again! I thought this would pretty obviously return the same empty array as the very first example I gave above, as per the syntax presented at http://docs.mongodb.org/manual/reference/operator/query/where/
I was trying the above out just to see if I could get anything to work with $where, but further problems arise when I try to add server-side JS to the mix. I had a look at
http://docs.mongodb.org/manual/tutorial/store-javascript-function-on-server/
and added the following to the system.js collection in my DB:
{
"_id": "isMatch",
"value": "function(){return false}"
}
and modified the above example with the line
collection.find( {$where: 'isMatch()' } ).toArray(function(err, matches){...
thinking that isMatch would now be in mongo's bag of tricks when it eval's the string (I assume it must be eval'ing the string), and the string syntax was the best bet from the playings around described above. Sadly this results in a console log of null - another flavour of wrong, I expected again an empty array.
Can anyone help me achieve the query I described above? I suspect I can work out the nitty gritty myself if you kind folks can help me understand 1. How to use the function syntax instead of the string syntax for $where, and 2. How to correctly declare a JS function in system.js for use with $where. Thanks in advance!
Your $where queries work fine when I try them in the shell, but you don't need to use $where for this query. Instead, you can use an $in that targets your fruits field to find docs that contain at least one matching fruit:
var fruits = ['apple', 'orange', 'lemon'];
db.test.find({fruits: {$in: fruits}});
Don't quite seem to know what was the case in 2013, but now I think you have to use $elemMatch along with $in that targets your fruits field to find docs that contain at least one matching fruit:
var fruits = ['apple', 'orange', 'lemon'];
db.test.find({fruits: {$elemMatch: {$in: fruits}}});

sorting alpha with mongoose

I'm trying to sort via mongoose 3.6.20 and I am receiving some unexpected results.
I have a list of companies with a name. At first I thought that maybe it was sorting in a case sensitive way. Which based on articles, I expect was true.
I'm now using a virtual property to down case the sort field. However, I'm still getting unexpected results.
CompanySchema.virtual('name_lower').get(function(){
return this.name.toLowerCase();
});
and when I sort
Company.find().sort({ name_lower: 1 });
I'm getting it in the following order:
company name
google
company name (yes a duplicate for testing)
I'm also outputting the value of my virtual property and it looks right. There is no whitespace or funky characters that would result in the 2nd 'company name' from appearing after google.
Using nodejs, express, mongoose.
What am I missing or doing incorrectly?
Update:
Based on the information provided in the answers, I refactored my schema to include some normalized fields and hooked into the pre save event of my document, where I update those normalized fields and sort using them in all future queries.
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
});
Next, is in the schema I use:
var CompanySchema = mongoose.Schema({
...
normalized_name: { type: String, set: normalize },
...
});
Where normalize is a function that for now, returns a lowercase version of the value passed into it. However, this allows me to expand on it later really fast, and I can quickly do the same to other fields that I might need to sort against.
As of MongoDB v3.4, case insensitive sorting can be done using collation method:
Company.find()
.collation({locale: "en" }) //or whatever collation you want
.sort({name:'asc'})
.exec(function(err, results) {
// use your case insensitive sorted results
});
Unfortunately MongoDB and Mongoose does not currently support complex sorting, so there are 2 options:
As you said, create a new field with the names sanitized to be all lowercase
Run a big for loop over all the data and update each company name to it's lower case form:
db.CompanyCollection.find().forEach(
function(e) {
e.CompanyName = e.CompanyName.toLowerCase();
db.CompanyCollection.save(e);
}
)
or
db.CompanyCollection.update({_id: e._id}, {$set: {CompanyName: e.CompanyName.toLowerCase()
Please see Update MongoDB collection using $toLower and Mongoose: Sort alphabetically as well for more info.
I want to put out that in this hook:
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
});
You'll have to call next(); at the end, if you want the normalized_name to be saved in the database, so the pre save hook would look like:
CompanySchema.pre('save', function(next){
this.normalized_name = this.name;
next();
});
This answer seems to be more helpful to me. I had to consider diacritics along with the case so I had used strength:3.
Mongoose: Sort alphabetically

Resources