mongoose invalid operator $match - node.js

I am going to handle users search based on email_address, firstname, lastname, type and phone_number.
phone_number search will be exact search with & without country code while others will be containing & case-insensitive search.
So, I wrote the below code.
User.aggregate(
[
{
"$redact": {
"$cond": [
{
"$and": [
{
"$match": {
type: req.body.type,
email_address: new RegExp((req.body.email_address || req.body.any || '').toLowerCase(), "i"),
"firstname": new RegExp((req.body.firstname || req.body.any || '').toLowerCase(), "i") ,
"lastname": new RegExp((req.body.lastname || req.body.any || '').toLowerCase(), "i")
}
},
{
"$or": [
{
"$setIsSubset": [
[
{ "$substr": [ "$phone_number.local_number", 0, -1 ] }
],
[req.body.phone_number, req.body.any]
]
},
{
"$setIsSubset": [
[
{
"$concat": [
{ "$substr": [ "$phone_number.country_code", 0, -1 ] },
{ "$substr": [ "$phone_number.local_number", 0, -1 ] }
]
}
],
[req.body.phone_number, req.body.any]
]
},
{}
]
}
]
},
"$$KEEP",
"$$PRUNE"
]
}
}
],
function(err, users) {
if (err) {
return res.json({ success: false, err: err });
}
res.json({ success: true, users: users });
}
);
But when I run this code, I get "invalid operator '$match'" error.
If I remove $match, it evaluate req.body values as expression instead of value and emit "FieldPath 'abc' doesn't start with $" kind error.
So, I hope to get help how to solve this problem and search by conditions.
Please help me !!!

Move the $match outside $redact as it's an independent pipeline stage, it will provide the initial filter with the regex that can otherwise be invalid within the $redact pipeline:
User.aggregate([
{
"$match": {
"type": req.body.type,
"email_address": new RegExp((req.body.email_address || req.body.any || '').toLowerCase(), "i"),
"firstname": new RegExp((req.body.firstname || req.body.any || '').toLowerCase(), "i") ,
"lastname": new RegExp((req.body.lastname || req.body.any || '').toLowerCase(), "i")
}
},
{
"$redact": {
"$cond": [
{
"$or": [
{
"$setIsSubset": [
[ { "$substr": [ "$phone_number.local_number", 0, -1 ] } ],
[req.body.phone_number, req.body.any]
]
},
{
"$setIsSubset": [
[
{
"$concat": [
{ "$substr": [ "$phone_number.country_code", 0, -1 ] },
{ "$substr": [ "$phone_number.local_number", 0, -1 ] }
]
}
],
[req.body.phone_number, req.body.any]
]
}
]
},
"$$KEEP",
"$$PRUNE"
]
}
}
], function(err, users) {
if (err) {
return res.json({ success: false, err: err });
}
res.json({ success: true, users: users });
}
);

Related

updateOne based on condition

await products.updateOne(
{
$and: [
{ name: { $eq: name } },
{ $expr: { $lt: ["$remaining", "$capacity"] } },
],
},
{ $inc: { remaining: 1 } },
{ returnOriginal: false }
);
Instead of having the condition in the query like so { $expr: { $lt: ["$remaining", "$capacity"] } }, is there a way to include this condition in the update argument?
The reason for this is so that I want the returned matchCount to return 1 if the name is matched.
Yes, you can do that if you use mongo 4.2+ using aggregate update.
db.collection.update({
$and: [ //condition goes here
{
name: {
$eq: "name"
}
},
],
},
[
{
"$set": { //conditional update
"remaining": {
"$switch": {
"branches": [
{
case: {
$lt: [ //condition to update
"$remaining",
"$capacity"
]
},
then: {
$add: [ //true case
"$remaining",
1
]
}
}
],
default: {
$add: [ //if no match
"$remaining",
0
]
}
}
}
}
}
])
playground

Find By ID and Remove From MongoDB array

My Collection:
geoGraphicalFilter: {
aCountries: [String],
aCities: [String],
aCoordinates: [{
coordinates: { type: Array }
}]
}
CollectionData
"geoGraphicalFilter": {
"aCoordinates": [
{
"_id": ObjectId("5acb641d93fa0e52557fc6aa"),
"coordinates": [
[
72.42919972527011,
23.0437703991947
],
[
72.45031407464302,
23.045823913521474
],
[
72.43263295281557,
23.030500782775746
],
[
72.42919972527011,
23.0437703991947
]
]
},
{
"_id": ObjectId("5acb641d93fa0e52557fc6ac"),
"coordinates": [
[
72.51520207511979,
23.038241551175616
],
[
72.55399754632015,
23.03934733892872
],
[
72.51812031852671,
23.025129376064214
],
[
72.51520207511979,
23.038241551175616
]
]
},
{
"_id": ObjectId("5acb641d93fa0e52557fc6ad"),
"coordinates": [
[
72.44653752434493,
23.02828905299478
],
[
72.4896245299627,
23.02828905299478
],
[
72.45477727044641,
23.0194417709901
],
[
72.44653752434493,
23.02828905299478
]
]
},
{
"_id": ObjectId("5acb641d93fa0e52557fc6ab"),
"coordinates": [
[
72.47451832878957,
23.045350028380867
],
[
72.50576069939376,
23.04835127278581
],
[
72.47949650871226,
23.031606634051897
],
[
72.47451832878957,
23.045350028380867
]
]
}
],
"aCities": [],
"aCountries": []
}
Remove From Database Snippet
const deleteZones = (req,res,next) => {
var body = _.pick(req.body, ["zones"]);
var zoneList = body.zones;
debug(zoneList)
var promise = function() {
return new Promise(function(resolve, reject) {
zoneList.forEach(itemA => {
console.log(itemA.coordinates)
huntingModel.update(
{ _id: req.body.id },
{ $pull: { 'geoGraphicalFilter.aCoordinates': itemA.id} },
(error, success) => {
if (error) console.log(error);
console.log(success);
}
);
});
resolve();
});
};
promise().then(function() {
return res.status(200).jsonp({
message: adminMessages.succ_zone_removed
});
});
}
Now the scenario is like when I am trying to delete data it shows success message but data does not get deleted.
var object = {
id:this.id,
zones: this.zonesDelete // Contains list of id
};
I am getting object in a requested body and I want to find the document from a collection and delete the particular array element by finding an id in geoGraphicalFilter.aCoordinates and wants to remove it.
As per documentation of $pull operator you can either specify a value or a condition
i.e.
{ $pull: { <field1>: <value|condition>, <field2>: <value|condition>, ... } }
In your scenario you need to either specify complete value of one or more aCoordinates item object or an condition that matches one or more aCoordinates item
Add the condition where you match id of aCoordinates item i.e.
Use following pull condition to solve the issue:
huntingModel.update(
{ _id: req.body.id },
{ $pull: { 'geoGraphicalFilter.aCoordinates': {'_id' : ObjectId(itemA.id)}} },
(error, success) => {
if (error) console.log(error);
console.log(success);
}
);

How to include empty array when filtering an array

Here is my item model.
const itemSchema = new Schema({
name: String,
category: String,
occupied: [Number],
active: { type: Boolean, default: true },
});
I want to filter 'occupied' array. So I use aggregate and unwind 'occupied' field.
So I apply match query. And group by _id.
But if filtered 'occupied' array is empty, the item disappear.
Here is my code
Item.aggregate([
{ $match: {
active: true
}},
{ $unwind:
"$occupied",
},
{ $match: { $and: [
{ occupied: { $gte: 100 }},
{ occupied: { $lt: 200 }}
]}},
{ $group : {
_id: "$_id",
name: { $first: "$name"},
category: { $first: "$category"},
occupied: { $addToSet : "$occupied" }
}}
], (err, items) => {
if (err) throw err;
return res.json({ data: items });
});
Here is example data set
{
"_id" : ObjectId("59c1bced987fa30b7421a3eb"),
"name" : "printer1",
"category" : "printer",
"occupied" : [ 95, 100, 145, 200 ],
"active" : true
},
{
"_id" : ObjectId("59c2dbed992fb91b7421b1ad"),
"name" : "printer2",
"category" : "printer",
"occupied" : [ ],
"active" : true
}
The result above query
[
{
"_id" : ObjectId("59c1bced987fa30b7421a3eb"),
"name" : "printer1",
"category" : "printer",
"occupied" : [ 100, 145 ],
"active" : true
}
]
and the result I want
[
{
"_id" : ObjectId("59c1bced987fa30b7421a3eb"),
"name" : "printer1",
"category" : "printer",
"occupied" : [ 100, 145 ],
"active" : true
},
{
"_id" : ObjectId("59c2dbed992fb91b7421b1ad"),
"name" : "printer2",
"category" : "printer",
"occupied" : [ ],
"active" : true
}
]
how could I do this??
Thanks in advance.
In the simplest form, you keep it simply by not using $unwind in the first place. Your conditions applied imply that you are looking for the "unique set" of matches to specific values.
For this you instead use $filter, and a "set operator" like $setUnion to reduce the input values to a "set" in the first place:
Item.aggregate([
{ "$match": { "active": true } },
{ "$project": {
"name": 1,
"category": 1,
"occupied": {
"$filter": {
"input": { "$setUnion": [ "$occupied", []] },
"as": "o",
"cond": {
"$and": [
{ "$gte": ["$$o", 100 ] },
{ "$lt": ["$$o", 200] }
]
}
}
}
}}
], (err, items) => {
if (err) throw err;
return res.json({ data: items });
});
Both have been around since MongoDB v3, so it's pretty common practice to do things this way.
If for some reason you were still using MongoDB 2.6, then you could apply $map and $setDifference instead:
Item.aggregate([
{ "$match": { "active": true } },
{ "$project": {
"name": 1,
"category": 1,
"occupied": {
"$setDifference": [
{ "$map": {
"input": "$occupied",
"as": "o",
"in": {
"$cond": {
"if": {
"$and": [
{ "$gte": ["$$o", 100 ] },
{ "$lt": ["$$o", 200] }
]
},
"then": "$$o",
"else": false
}
}
}},
[false]
]
}
}}
], (err, items) => {
if (err) throw err;
return res.json({ data: items });
});
It's the same "unique set" result as pulling the array apart, filtering the items and putting it back together with $addToSet. The difference being that its far more efficient, and retains ( or produces ) an empty array without any issues.

mongoose user bulk search by phone numbers

I have UserSchema which contains PhoneNumberSchema as below.
var PhoneNumberSchema = new Schema({
country: {
type: String
},
country_code: {
type: Number
},
local_number: {
type: Number
}
});
And here is sample json format of phone_number.
"phone_number": {
"country": "US",
"country_code": "1",
"local_number": "04152341"
}
What I want to do is to search users by phone numbers with / without country code.
Well, if request is
"phone_numbers": [ "104152341", "124254364" ]
then I want to get users who has exactly matched phone number which belongs in the request phone numbers array with/without country code.
So, I tried as below, but got error "invalid operator '$in'".
User.aggregate(
[
{ "$redact": {
"$cond": [
{
"$in": [ { "$concat": [ "$phone_number.country_code", "$phone_number.local_number" ] }, req.body.phone_numbers]
},
"$$KEEP",
"$$PRUNE"
]
}}
],
function(err, users) {
// Do something
if (err) {
return res.json({ success: false, err: err });
}
res.json({ success: true, users: users });
}
)
I hope to know how to handle my issue.
Please help me !!
Use $setIsSubset as your condition expression:
{ "$redact": {
"$cond": [
{
"$setIsSubset": [
[
{
"$concat": [
"$phone_number.country_code",
"$phone_number.local_number"
]
}
],
req.body.phone_numbers
]
},
"$$KEEP",
"$$PRUNE"
]
}}

MongoDB - $nin invalid operator with aggregate

I have a function that should return a count of users created in a given period and group by invited and non invited. On the $cond operator, I need to compare if the field tkbSponsor is not null and is not equals example#example.com. If this condition results to true, then the user was invited. Otherwise, he wasn't invited.
var countByPeriod = function(req,res) {
var initialDate = req.body.initialDate;
var finalDate = req.body.finalDate;
User.aggregate([
{
"$match": {
"timeStamp": {
"$gte": new Date(initialDate),
"$lt": new Date(finalDate)
}
}
},
{
"$group": {
"_id": null,
"total": { "$sum": 1 },
"invited": {
"$sum": {
"$cond": [
{
"tkbSponsor": {
"$nin": ["example#example.com",null]
}
},
1,
0
]
}
}
}
}
], (err,result) => {
if (!err) {
if (result.length) res.send(result[0]);
else res.send({"total": 0,"invited":0});
} else {
res.sendStatus(500);
console.log(err);
}
});
};
By the way, this function is giving me an error when executed:
{ [MongoError: invalid operator '$nin']
name: 'MongoError',
message: 'invalid operator \'$nin\'',
ok: 0,
errmsg: 'invalid operator \'$nin\'',
code: 15999 }
Just an observation. I used to use the $cond operator as below, because I didn't needed to compare with null:
"$cond": [
{
"$ne": ["$tkbSponsor", "example#example.com"]
},
1,
0
]
And it works. However, now I have also to compare if the tkbSponsor is not null and using $nin, is giving me that error.
Change that to use $and together with the $ifNull coalesce as :
{
"$cond": [
{
"$and": [
{ "$ne": ["$tkbSponsor", "example#example.com"] },
{
"$ne": [
{ "$ifNull": [ "$tkbSponsor", null ] },
null
]
}
]
}, 1, 0
]
}
or using $or as
{
"$cond": [
{
"$or": [
{ "$eq": ["$tkbSponsor", "example#example.com"] },
{
"$eq": [
{ "$ifNull": [ "$tkbSponsor", null ] },
null
]
}
]
}, 0, 1
]
}
The $ifNull operator's presence is to act as an $exists operator by replacing "non-existant" or null fields with a null value for evaluation.
Running this should return the correct results as the earlier revision was only evaluating documents where the tkbSponsor field exists AND has either a value of null or "example#example.com".
With $ifNull, "non-existant" fields are also evaluated as the operator gives them the null value.

Resources