I need to build a query using the following sample data:
{
brandFilters:[ { brand: 'Amana', checked: true },
{ brand: 'Whirlpool', checked: true } ],
priceFilters:[ { low: 100, high: 200, label: '$100 - $200', checked: true },
{ low: 200, high: 300, label: '$200 - $300', checked: true } ],
rating: null }
The first segment of the query builds the brands criteria as shown here;
let brands = [];
let productQuery = Product.find();
if(filters.brandFilters.length) {
for(let filter of filters.brandFilters) {
brands.push(filter.brand)
}
productQuery.where('brand').in(brands)
console.log('brands', brands)
}
The second segment is supposed build the price criteria as shown here:
if(filters.priceFilters.length) {
for(let filter of filters.priceFilters) {
productQuery.where('price').gte(+filter.low).lte(+filter.high);
}
}
The intention is to add a "where" clause for each price filter as shown in the data above. The problem is multiple where clauses for the price criteria are not being added. Each successive price filter in the for loop overwrites the last. So instead of having multiple "where" clauses for my price filters, the query only contains one for the last iteration of the for loop.
I am trying to get to a query that would look like this:
productQuery.where('brand').in(['Amana', 'Whirlpool'])
productQuery.where('price').gte(100).lte(200)
productQuery.where('price').gte(200).lte(300)
So the query you're trying to build should look like below:
{
"$and":[
{"$or":[
{"price":{"$gte":100,"$lte":200}},
{"price":{"$gte":200,"$lte":300}}
]
},
{"brand":{"$in":["Amana","Whirlpool"]}}
]
}
So it contains $or to include multiple price ranges. To do that you can build that query as plain JavaScript object and then pass it to find method, try:
let query = {
'$and': []
};
var priceQuery = [];
let brands = [];
if(filters.priceFilters.length) {
for(let filter of filters.priceFilters) {
priceQuery.push({ 'price': { $gte: +filter.low, $lte: +filter.high } });
}
query['$and'].push({ '$or': priceQuery });
};
if(filters.brandFilters.length) {
for(let filter of filters.brandFilters) {
brands.push(filter.brand)
}
query['$and'].push({ 'brand': { '$in': brands } });
}
and then
let products = await Product.find(query);
Related
I have a model:
const schema = new Schema({
// ....
conditions: {},
// ....
});
Conditions - nested document and I can save anything into it with any key. And let's say we have such conditions:
{
"conditions": {
"age": 10,
"name": "John"
}
}
This is located on the base. Now, I want to find this document, but since I don't know what fields are there, I am facing problems...
const conditions = {
'conditions.age': 10,
'conditions.name': 'John',
'conditions.surname': 'White' // surname doesn't exists
}
const result = await Model.find(conditions);
console.log(result) // [];
And the question is, is it possible to exclude from the filter the fields that are missing in the document? So that find() simply skipped them, did not take them into account...
Use Logical Query Operators $and and $or as below-
const conditions = {
$and: [
{ 'conditions.age': 10, },
{ 'conditions.name': 'John', },
{ $or: [{ 'conditions.surname': { $exists: false } }, { 'conditions.surname': 'White' }] }
]
}
const result = await Model.find(conditions);
Is there a way to search a mongodb database using an array of objects as search arguments?
Lets say I have the following search preferences:
preferences = [{
product: "PRODUCT_A",
minqty: 5,
maxqty: 50
},
{
product: "PRODUCT_B",
minqty: 100,
maxqty: 500
}
]
In my database I have Jobs with the following structure:
{
jobName: "job name",
items: [{
product: "PRODUCT_A"
qty: 25
},
{
product: "PRODUCT_F"
qty: 300
}
]
}
I would like to query the database using preferences and returning any jobs that match at least one of the criteria's.
ATTEMPT 1:
I managed to use all my preferences as filters, but $match is cumulative, the way it's written it works like && in javascript. My goal is to have "match THIS || match THAT".
let pipeline = [];
preferences.map((item) => {
let search = {
product: item.product,
quantity: { $gte: item.minqty, $lte: item.maxqty },
};
return pipeline.push({
$match: {
items: {
$elemMatch: search,
},
},
});
});
const jobs = await Job.aggregate(pipeline);
ATTEMPS 2 (SUCCESS):
let search = [];
preferences.map((item, index) => {
let arguments = {
product: item.product,
quantity: { $gte: item.minqty, $lte: item.maxqty },
};
search.push(arguments);
});
let pipeline = [
{
$match: {
items: {
$elemMatch: {
$or: search,
},
},
},
},
];
const jobs = await Job.aggregate(pipeline);
I think you can create your search object by reducing the preferences array and use the $or operator. When you map the preferences array it is returning an array that will perform and operation. you need an object like -
{
$or: [{product1, quantity1}, {product2, quantity2}]
}
I guess you got my point.
I have to deal with objects of the following type in a NodeJS app (using mongodb driver):
data_test = {
"id": "105-20090412",
"date": new Date('2020-09-04T14:00:00.000Z'),
"station": {
"name": "AQ105",
"loc": {
"type": "Point",
"coordinates": [14.324498, 40.821930]
},
"properties": {}
},
"samples": [{
"t": new Date('2020-09-04T14:14:00.000Z'),
"data": {
//"temp_celsius": 31.81,
//"humRelPercent": 39,
"press_mBar": 1021.12,
"PM10": 200
}
}]
}
I receive every 2 minutes data as above.
I want to:
If the data received has an id not yet present on MongoDB do an insert
If the data received has a sample object with a Date (t property) yet present then add properties to this one (for example readings of different sensors)
If the data received has a sample object with a Date (t property) not yet present in samples array, then add this new one
I would like to do what described above with the minor count possible of round-trips to the MongoDB server.
I hope to have been clear enough.
Any suggestion?
Thanks in advance.
Here's my suggestion, this is not the correct answer. You will need to fiddle with the query portion. The query below should work for 1 & 3, for 2 you will have to play around.
db.collection.updateOne(
{ "id" : "105-20090412", "samples.t": <Date> },
{ $push: { "samples" : <sample> } },
{ $setOnInsert: { station: <station> } },
{ upsert: true }
);
References:
https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/
https://docs.mongodb.com/manual/reference/operator/update/setOnInsert/#up._S_setOnInsert
https://docs.mongodb.com/manual/reference/operator/update/push/
I finally came to the following solution, perhaps not the most efficient one:
try {
const db = client.db(dbName);
const collection = db.collection(collectionName);
// retrive id, station, date and samplesToAdd as separate objects
let {
id,
...dataToInsert
} = data
//id = new ObjectID(id)
const queryBy_id = {
_id: id
}
// first check if doc exists
let res_query = await collection.findOne(queryBy_id)
// if doc does not exists then insert a new one
if (!res_query) {
res_insert = await collection.insertOne({
_id: id,
...dataToInsert
})
return res_insert;
} else {
// retrive samples from initial query
let current_samples = res_query.samples
// check if sample in dataToInsert yet exists
// use getTime to correctly compare dates
let idx = current_samples.findIndex(x => x.t.getTime() == dataToInsert.samples[0].t.getTime())
if (idx >= 0) {
// find index of sample to update
let current_t = current_samples[idx].t
// merge data yet stored with new one
current_samples.data = {
...current_samples[idx].data,
...dataToInsert.samples[0].data
}
let resUpdateSample = await collection.updateOne({
_id: id,
'samples.t': current_t
}, {
$set: {
'samples.$.data': current_samples.data
}
})
return resUpdateSample
} else {
// add data to samples array
let resAddToSamples = await collection.updateOne({
_id: id
}, {
$push: {
samples: dataToInsert.samples[0]
}
})
return resAddToSamples
}
}
} catch (err) {
logger.error(err);
}
How can I improve it?
Thanks.
I am trying to find specific fields for more than one value. For example, I have a database with different countries and I am trying to retrieve their name, year, and nominalGDP (renamed to y in the result for some other important reason). It works perfect for this example, where I am only retrieving from USA, but how would I add another country like China or whatever?
Country.aggregate([
{
$match: {
name: "USA"
}
},
{
$project: {
_id: 0,
name: 1,
year: 1,
'y' : '$nominalGDP'
}
}
], function(err, recs){
if(err){
console.log(err);
} else {
console.log(recs);
}
});
This is probably really simple but I have not been able to find out how.
Use $in operator to specify more than one matching option. For example:
{
$match: {
name: { $in: [ "USA", "China" ] }
}
}
`const results = await SchemaName.find([{$match:{name:{$in:["USA","China"]}}}])
res.status(200).json(results);`
but if you are getting the country names from frontend through the body or something then:
` const allCountries = req.body.allCountries;
var result;
for(let i=0; i < allCountries.length; i++){
result = await SchemaName.find([{$match:{name:{$in:allCountries[i]}}}])
}
`
assuming you are building asynchronous functions...
I have simple structure like this:
{
_id: "4f23f23f432f43" //random _id
list: [
{
price: 8
},
{
price: 13
},
{
price: 17
},
]
}
Above example is simple schema, which in base is list of few objects.
My problem is that I cannot get this to work:
dbQuery.menu = {
$elemMatch : {
price: {
$gte: request.query.minPrice
}
}
}
I need to find only these documents which all array elements matches this query. Meaning, return object only when it has list which contains only values greater (or equal) than specified query value.
Now it returns all object which at least one list element is higher than specified value, which is wrong.
UPDATE:
If you want to add a maxPrice field to the query condition, it must be a an $or-combined statement, not $and. Because n >= minPrice && n <= maxPrice should equal to !(n < minPrice || n > maxPrice).
So the query now should look like:
YourModel.find({
'list': {
$not: {
$elemMatch: {
'price': {
$or: [
{ $lt: request.query.minPrice },
{ $gt: request.query.maxPrice }
]
}
}
}
}
});
=====
(This is old answer)
Try this:
YourModel.find({
'list': {
$not: {
$elemMatch: {
'price': {
$lt: request.query.minPrice
}
}
}
}
});