Workaround for projection issue in mongo? - node.js

We have project where we used projection like this:
const filter = { id };
const projection = {
providers: { $elemMatch: { providerId: ObjectId(providerId) } },
"providers.payments": 1,
"providers.tax.vatPayer": 1,
"providers.tax.vat": 1
}
return super.findOne(filter, projection);
As you may know, this is illegal after 4.4. Is there any workaround how to filter specific item in array and then specify which fields of this array to project?
What I've tried:
const filter = { id, "providers.providerId": ObjectId(providerId) };
const projection = {
"providers.$.payments": 1,
"providers.$.tax.vatPayer": 1,
"providers.$.tax.vat": 1
}
return super.findOne(filter, projection);
const filter = { id, "providers.providerId": ObjectId(providerId) };
const projection = {
"providers.$": 1,
"providers.payments": 1,
"providers.tax.vatPayer": 1,
"providers.tax.vat": 1
}
return super.findOne(filter, projection);
const filter = { id };
const projection = {
"providers.$.payments": 1,
"providers.$.tax.vatPayer": 1,
"providers.$.tax.vat": 1,
providers: { $elemMatch: { providerId: ObjectId(providerId) } }
}
return super.findOne(filter, projection);
const filter = { id };
const projection = {
providers: {
$elemMatch: { providerId: ObjectId(providerId) },
payments: 1,
"tax.vatPayer": 1,
"tax.vat": 1
}
}
return super.findOne(filter, projection);
Thanks a lot for any suggestions!

$elemMatch in projection
The $elemMatch operator limits the contents of an <array> field from the query results to contain only the first element matching the $elemMatch condition.
return super.findOne(
{ id: id },
{ providers: { $elemMatch: { providerId: ObjectId(providerId) } } }
)
Playground
$ projection
The positional $ operator limits the contents of an <array> to return either:
The first element that matches the query condition on the array.
The first element if no query condition is specified for the array (Starting in MongoDB 4.4).
Both the $ operator and the $elemMatch operator project the first matching element from an array based on a condition.
$elemMatch in match condition and $ projection
return super.findOne(
{ id: id, providers: { $elemMatch: { providerId: ObjectId(providerId) } } },
{ "providers.$": 1 }
)
Playground
dot(.) notation in match condition and $ projection
return super.findOne(
{ id: id, "providers.providerId": ObjectId(providerId) },
{ "providers.$": 1 }
)
Playground
Positional Operator Placement Restriction here:
Starting in MongoDB 4.4, the $ projection operator can only appear at the end of the field path; e.g. "field.$" or "fieldA.fieldB.$".
Positional Operator and $slice Restriction here:
Starting in MongoDB 4.4, find and findAndModify projection cannot include $slice projection expression as part of a $ projection expression.

Related

MongoDB query an array of documents for specific match

I want to search the transactions array looking for a specific match. In this example, by pipedrive_id.
This is what I tried (as per mongodb instructions and this other stack overflow post)
const pipedrive_id = 1677;
const inner_pipedrive_id = 1838;
const result = await Transactions.find({
pipedrive_id,
'transactions': { $elemMatch: { 'pipedrive_id': inner_pipedrive_id } }
});
const result2= await Transactions.find({
'transactions': { $elemMatch: { 'pipedrive_id': inner_pipedrive_id } }
});
const result3 = await Transactions.find({
'transactions.pipedrive_id': inner_pipedrive_id
});
And each result itteration returns all transaction items (all 6 items, instead of 2 [that's how many Mark Smith has in the array).
You can use aggregate to filter out the array. Something like this
You can remove $project if you want all the fields
db.collection.aggregate([
{
$match: {
pipedrive_id: "1677"
}
},
{
$unwind: "$transactions"
},
{
$match: {
"transactions.pipedrive_id": "1838"
}
},
{
$project: {
_id: 0,
pipedrive_id: 1,
transactions: 1
}
}
])
You can check the Mongo playground here.
As the doc, $elemMatch matches documents that contain an array field with at least one element that matches the criteria.
To filter the result inside the array, you will need to use $filter from aggregation
Ref: https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/

How can I update a document's nested array of object item by 1?

I'm trying to update a mongoose document with the help of findOneAndUpdate but I'm unable to do so. The document looks like this in the database:
{
'docId': 1001,
'totalViews': 3,
'docInfo': [
{
id: 1,
views: 2
},
{
id: 2,
views: 1
}
]
}
I'm trying to update totalViews by 1 which will make the total count to be 4. And I also need to update the second object's views property by 1 in imageInfo array. Which will have a views count of 2.
I tried doing this by first fetching the whole document with the help of:
const doc = await Doc.find({ docId: 1001 });
Then found the index of the docInfo array item which needs to be updated. Which is the object with id 2.
const docIndex = doc[0].docInfo.findIndex( item => {
return item.id === 2;
});
Then used findOneAndUpdate to update the items:
await Doc.findOneAndUpdate(
{ docId: 1001, "docInfo.id": 2 },
{
$set: {
[ `docInfo.${2}.views` ]: 1++,
'totalViews': 1++
}
}, { new: true }
);
With this I'm getting this error:
SyntaxError: Invalid left-hand side expression in postfix operation
What am I doing wrong here?
What you are doing is invalid, you can use $inc operator to increment a number, and don't need to find a query as well,
await Doc.findOneAndUpdate(
{ docId: 1001, "docInfo.id": 2 },
{
$inc: {
'docInfo.$.views': 1,
'totalViews': 1
}
},
{ new: true }
);
Playground

Mongodb query params ( combine $gt with $in )

I'm using api query params library
rooms=2,4,5&rooms>=6
this gives
rooms: { '$gte': 6, '$in': [ 2, 4, 5 ] }
This won't work but if i search with only rooms>=6 it works or with rooms=2,4,5.
How i can combine $gte with $in ?
It sounds like you want to OR if the same key has multiple conditions. (I want all rooms, 2, 4, 5 OR greater than 6.)
To do that in mongo:
$or: [
{rooms: { $gte: 6}},
{rooms: { $in: [2, 4, 5]}}
]
You can also use not in for your query like
db.inventory.find( { rooms: { $nin: [ 1, 6] } } )
So guys i did this with $nin operator.
First i'm using api-query-params library.
let { filter, skip, limit, sort } = aqp(req.query)
It can parse query param --- rooms=1,2,3,4,5
into 'rooms': { '$in': [ 4, 3, 2, 1, 0, 5 ] }
But if we want when user adds 5 to be $gte 5
function addOrOperator(filter, field) {
//if selected value is undefined return
if(!filter[field]) return;
//values to match
let array = [0,1,2,3,4,5];
//if 5 is only selected returned value is 'rooms': 5
if(filter[field] === 5) {
filter[field] = {
$gte: 5
}
}
if(filter[field].$in) {
if(filter[field].$in.includes(5)) {
let difference = array.filter(x => !filter[field].$in.includes(x));
filter[field] = {
$nin: [...difference, null, ""]
}
}
}
return filter;
}
Then we are calling this function inside controller
addOrOperator(filter, 'rooms'); // all filters and key that we want to select

MongoDB return nested array of objects minus a property within the object (also .then() doesn't work with aggregation)

I'm very new to mongodb and am having difficulty getting to a solution for my use case. For example I have the following document:
{
_id : ObjectId('5rtgwr6gsrtbsr6hsfbsr6bdrfyb'),
uuid : 'something',
mainArray : [
{
id : 1,
title: 'A',
array: ['lots','off','stuff']
},
{
id : 2,
title: 'B',
array: ['even','more','stuff']
}
]
}
I'd like to have the following returned:
{
uuid : 'something',
mainArray : [
{
id : 1,
title: 'A'
},
{
id : 2,
title: 'B'
}
]
}
I've tried various combinations of using findOne() and aggregate() with $slice and $project. With findOne(), if it worked at all, the who document would be returned. I am unable to test whether attempts at aggregating work because .then((ret)=>{}) promises don't seem to work in node.js for me with it (no issues with findOne). Calling a function like so
return db.myCollection.aggregate([
{
$match: {
_id:ObjectId(mongo_id)
}
},
{
$project : {
mainArray: {
id:1,
title:1
}
}
}
],function(err,res){
console.log(res)
return res
})
logs the entire function and not the droids I'm looking for.
You're missing toArray() method to obtain the actual result set. Instead you're returning the aggregation cursor object. Try this.
return db.myCollection.aggregate([matchCode,projectCode]).toArray().then(
data => {
console.log(data);
return data;
},
error => { console.log(error)});
The documnetation on aggregation cursor for MongoDB NodeJS driver can
be found here
http://mongodb.github.io/node-mongodb-native/3.5/api/AggregationCursor.html#toArray
This is an alternative solution (to the solution mentioned in the comment by #v1shva)
Instead of using aggregation you can use projection option of .findOne() operation.
db.myCollection.findOne(matchCode, {
projection: { _id: false, 'mainArray.array': false } // or { _id: -1, 'mainArray.array': -1 }
})

Mongoose return document only if all sub-list elements match query

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
}
}
}
}
});

Resources