Find with arrayFilters using Mongoose - node.js

I have to filter the object which contains only status C in comments(If atleast only comment have the status C then that object alone should be print) I tried using array Filters but I don't get exact result
{
"_id" : ObjectId("5b8f84379f432a42383a85bb"),
"projectID" : ObjectId("00000000e614c33390237ce3"),
"inspection_data" : [
{
"locationAspects" : [
{
"aspectname" : "Ground floor",
"comments" : [
{
"status" :"C",
"comment" : [
"good"
],
"_id" : ObjectId("5b8f84379f16400f884d9974")
}
],
"_id" : ObjectId("5b8f84379f16400f884d9975")
},
{
"aspectname" : "Second floor",
"comments" : [
{
"status" :"P",
"comment" : [
"nothing"
],
"_id" : ObjectId("5b8f84379f16400f884d9971")
}
],
"_id" : ObjectId("5b8f84379f16400f884d9972")
},
],
"published_date" : ISODate("2018-09-05T07:22:31.017Z"),
"_id" : ObjectId("5b8f84379f16400f884d9976")
},
{
"locationAspects" : [
{
"aspectname" : "Ground floor",
"comments" : [
{
"status" :"P",
"comment" : [
"good"
],
"_id" : ObjectId("5b8f84379f16400f884d9974")
}
],
"_id" : ObjectId("5b8f84379f16400f884d9975")
}
],
"published_date" : ISODate("2018-09-05T07:22:31.017Z"),
"_id" : ObjectId("5b8f84379f16400f884d9976")
}
]
Now the inspection data having two object but one object only containing comment status c, So That should be print
Expected Result
[ {
"locationAspects" : [
{
"aspectname" : "Ground floor",
"comments" : [
{
"status" :"C",
"comment" : [
"good"
],
"_id" : ObjectId("5b8f84379f16400f884d9974")
}
],
"_id" : ObjectId("5b8f84379f16400f884d9975")
},
{
"aspectname" : "Second floor",
"comments" : [
{
"status" :"P",
"comment" : [
"nothing"
],
"_id" : ObjectId("5b8f84379f16400f884d9971")
}
],
"_id" : ObjectId("5b8f84379f16400f884d9972")
},
],
"published_date" : ISODate("2018-09-05T07:22:31.017Z"),
"_id" : ObjectId("5b8f84379f16400f884d9976")
}]
Above object only having the status C if alteast one comment status is C that object alone have to display

You need $filter to process inspection_data. Things are getting complicated since you have multiple levels of nestings, so before you can apply $in condition you need to get an array of all statuses for single inspection_data. To achieve that you can use direct path like "$$data.locationAspects.comments.status" but it returns an array of arrays like this:
[ [ "C" ], [ "P" ] ], [ [ "P" ] ] ]
So you have to flatten that array and that can be achieved using $reduce and $concatArrays. Try:
db.col.aggregate([
{ $match: { projectID: ObjectId("00000000e614c33390237ce3") } },
{
$project: {
filtered_inspection_data: {
$filter: {
input: "$inspection_data",
as: "data",
cond: {
$let: {
vars: {
statuses: {
$reduce: {
input: "$$data.locationAspects.comments.status",
initialValue: [],
in: { $concatArrays: [ "$$this", "$$value" ] }
}
}
},
in: { $in: [ "C", "$$statuses" ] }
}
}
}
}
}
}
])
EDIT: to match "C" or "P" you can use replace { $in: [ "C", "$$statuses" ] } with following line
{ $or: [ { $in: [ "C", "$$statuses" ] }, { $in: [ "P", "$$statuses" ] } ] }

Related

MongoDB object property check if $exists with certain value in nested array

I have a DB collection like this.
{
"name" : "test",
"gender" : "male",
"attributes" : [
{
"field_id" : "123",
"field_value" : "['Public']"
},
{
"field_id" : "124",
"field_value" : "true"
},
{
"field_id" : "125",
"field_value" : "['Single']"
},
]
},
{
"name" : "test2",
"gender" : "male",
"attributes" : [
{
"field_id" : "125",
"field_value" : "['Married']"
},
]
},
{
"name" : "test3",
"gender" : "male",
"attributes" : [
{
"field_id" : "123",
"field_value" : "['Public']"
},
{
"field_id" : "125",
"field_value" : "['Married']"
},
]
},
{
"name" : "test4",
"gender" : "male",
"attributes" : [
{
"field_id" : "123",
"field_value" : "['Private']"
},
]
},
{
"name" : "test4",
"gender" : "male",
"attributes" : [
]
}
]
I want to fetch all records which have field_id as 123 and field_value as "Public"; + records in which field_id
123 doesn't exist under attributes.
I tried
var condition = { "attributes" : {$elemMatch:{field_id:"123",field_value:{ $in:["Public"] }}}};
queryObj.push(condition);
this works fine to fetch records having field_id as 123 and field_value as Public
But I also want to get records in which field_id as 123 doesn't exists, I tried this
var condition1 =
{ "attributes" : {$elemMatch:{field_id:"123",$exists:false}}}
queryObj.push(condition1);
but it returns an error as it's not right syntax.
Please guide how do I fetch as desired.
This is the expected output, As these records have only Public Value for 123 field_id or field_id as 123 DOES NOT EXIST in these.
[
{
"name" : "test",
"gender" : "male",
"attributes" : [
{
"field_id" : "123",
"field_value" : "['Public']"
},
{
"field_id" : "124",
"field_value" : "true"
},
{
"field_id" : "125",
"field_value" : "['Single']"
},
]
},
{
"name" : "test3",
"gender" : "male",
"attributes" : [
{
"field_id" : "123",
"field_value" : "['Public']"
},
{
"field_id" : "125",
"field_value" : "['Married']"
},
]
},
{
"name" : "test2",
"gender" : "male",
"attributes" : [
{
"field_id" : "125",
"field_value" : "['Married']"
},
]
},
{
"name" : "test4",
"gender" : "male",
"attributes" : [
]
}
]
you should use two $elemMatch with $or operator.
your query should be something like this.
db.collection.find({
$or: [
{
"attributes": {
"$elemMatch": {
field_id: "123",
field_value: "['Public']"
}
}
},
{
"attributes": {
"$not": {
"$elemMatch": {
field_id: "123"
}
}
}
}
]
})
working example: https://mongoplayground.net/p/CP3HSKmNpKM

Conditionally count foreign fields in collection with Mongo aggregations [duplicate]

I have a mongoDB collection called "conference" with an array of participants as below :
[
{
"_id" : 5b894357a0c84d5a5d221f25,
"conferenceName" : "myFirstConference",
"startDate" : 1535722327,
"endDate" : 1535722420,
"participants" : [
{
"name" : "user1",
"origin" : "internal",
"ip" : "192.168.0.2"
},
{
"name" : "user2",
"origin" : "external",
"ip" : "172.20.0.3"
},
]
},
...
]
I would like to get the following result :
[
{
"conferenceName" : "myFirstConference",
"startDate" : 1535722327,
"endDate" : 1535722420,
"internalUsersCount" : 1
"externalUsersCount" : 1,
},
...
]
I tried the request below but it's not working :
db.getCollection("conference").aggregate([
{
$addFields: {
internalUsersCount : {
$size : { "$participants" : {$elemMatch : { origin : "internal" }}}
},
externalUsersCount : {
$size : { "$participants" : {$elemMatch : { origin : "external" }}}
}
}
}
])
How is it possible to count "participant" array elements that match {"origin" : "internal"} and {"origin" : "external"} ?
You need to use $filter aggregation to filter out the external origin and internal origin along with the $size aggregation to calculate the length of the arrays.
Something like this
db.collection.aggregate([
{ "$addFields": {
"internalUsersCount": {
"$size": {
"$filter": {
"input": "$participants",
"as": "part",
"cond": { "$eq": ["$$part.origin", "internal"]}
}
}
},
"externalUsersCount": {
"$size": {
"$filter": {
"input": "$participants",
"as": "part",
"cond": { "$eq": ["$$part.origin", "external"] }
}
}
}
}}
])
Output
[
{
"conferenceName": "myFirstConference",
"endDate": 1535722420,
"externalUsersCount": 1,
"internalUsersCount": 1,
"startDate": 1535722327
}
]

How to use $filter(aggregation) to select some fields of array only if condition true?

Here I'll show you what exactly I want. Suppose I have the below two document for XYZ model.
[
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"email" : "one#one.com",
"mobileNumber" : "910123456989",
"verificationStatus" : true,
"activities" : [
{
"name" : "a1",
"_id" : ObjectId("59ef8786e8c7d60552139bae"),
"type" : 0,
"level" : null,
"verificationStatus" : true
},
{
"name" : "a2",
"_id" : ObjectId("59ef8786e8c7d60552139bad"),
"type" : 0,
"level" : null,
"verificationStatus" : false
}
],
"address" : {
"line1" : "asd",
"line2" : "asd",
"city" : "sd",
"state" : "sd",
"country" : "asd",
"landmark" : "sdsa",
"pincode" : "560090"
},
"__v" : 0
},
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"email" : "one#one.com",
"mobileNumber" : "919876543210",
"verificationStatus" : true,
"activities" : [
{
"name" : "b1",
"_id" : ObjectId("59ef8786e8c7d60552139bae"),
"level" : null,
"type" : 0,
"verificationStatus" : true
},
{
"name" : "b2",
"_id" : ObjectId("59ef8786e8c7d60552139bad"),
"level" : null,
"type" : 0,
"verificationStatus" : false
}
],
"address" : {
"line1" : "asd",
"line2" : "asd",
"city" : "sd",
"state" : "sd",
"country" : "asd",
"landmark" : "sdsa",
"pincode" : "560090"
},
"__v" : 0
}
]
Now I want only the name, mobileNumber and activities.name from the document where verificationStatus is true and I don't want all the activities I want activities.name only if activities.varificationStatus is true.
I can get the list of all document where varificationStatus is true and activities.varificationStatus is true but I'm not able to select only required fields (activities.name) from activities.
My current code is:
XYZ.aggregate(
[
{ $match: { verificationStatus: true } },
{
$project: {
name: 1,
coverImage: 1,
location: 1,
address: 1,
dist: 1,
activities: {
$filter: {
input: "$activities",
as: "activity",
cond: {
$eq: ["$$activity.verificationStatus", true]
}
}
}
}
}], function (err, list) {
if (err) {
reject(err);
}
else {
resolve(list);
}
});
You actually need $map to "alter" the array elements returned, as $filter only "selects" the array elements that "match":
XYZ.aggregate(
[
{ $match: { verificationStatus: true } },
{
$project: {
name: 1,
mobileNumber: 1,
activities: {
$map: {
input: {
$filter: {
input: "$activities",
as: "activity",
cond: "$$activity.verificationStatus"
}
},
"as": "a",
"in": "$$a.name"
}
}
}
}], function (err, list) {
...
Would return:
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "910123456989",
"activities" : ["a1"]
}
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "919876543210",
"activities" : ["b1"]
}
Note also that the "cond" in $filter can be shortened since it's already a boolean value.
If you wanted the "object" with the property of "name" only, then return just that assigned key:
XYZ.aggregate(
[
{ $match: { verificationStatus: true } },
{
$project: {
name: 1,
mobileNumber: 1,
activities: {
$map: {
input: {
$filter: {
input: "$activities",
as: "activity",
cond: "$$activity.verificationStatus"
}
},
"as": "a",
"in": {
"name": "$$a.name"
}
}
}
}
}], function (err, list) {
...
Returns as:
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "910123456989",
"activities" : [{ "name": "a1" }]
}
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "919876543210",
"activities" : [{ "name": "b1" }]
}
If you knew for certain that you were matching "one" element in the array, then $indexOfArray with $arrayElemAt could be used instead if you have MongoDB 3.4
{ "$project": {
"name": 1,
"mobileNumber": 1,
"activities": {
"$arrayElemAt": [
"$activities.name",
{ "$indexOfArray": [ "$activities.verificationStatus", true ] }
]
}
}}
Which would come out a little differently since it's a singular value and not an array:
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "910123456989",
"activities" : "a1"
}
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "919876543210",
"activities" : "b1"
}

Multiple $group in mongoDB

I have in mongodb differents records. I write down a little example:
{_id:"sad547er4w2v5x85b8", name:"Jhon", jobTime:600, floor:2, dept:5, age:25},
{_id:"xcz547wer4xcvcx1g2", name:"Alex", jobTime:841, floor:4, dept:1, age:55},
{_id:"xcnwep2321954ldfsl", name:"Alice", jobTime:100, floor:3, dept:3, age:55},
{_id:"23s3ih94h548jhfk2u", name:"Anne", jobTime:280, floor:2, dept:8, age:22},
{_id:"03dfsk9342hjwq1503", name:"Alexa", jobTime:355, floor:2, dept:6, age:25}
I tried to obtain this output, but I don't know how to group by twice to get that structure.
{[
{age:22, floors:[{floor:2,persons:[{name:"Anne",jobTime:280,dept:8}]}]},
{age:25, floors:[{floor:2,persons:[{name:"Jhon",jobTime:600,dept:5},{name:"Alexa",jobTime:355,dept:6}]}]},
{age:55, floors:[{floor:3,persons:[{name:"Alex",jobTime:841,dept:1}]},{floor:4,persons:[{name:"Alice",jobTime:100,dept:3}]}]}
]}
Exactly. Use "two" $group stages
collection.aggregate([
{ "$group": {
"_id": {
"age": "$age",
"floor": "$floor",
},
"persons": { "$push": {
"name": "$name",
"jobTime": "$jobTime",
"dept": "$dept"
}}
}},
{ "$group": {
"_id": "$_id.age",
"floors": { "$push": {
"floor": "$_id.floor",
"persons": "$persons"
}}
}}
],function(err,results) {
// deal with results here
})
Which produces:
{
"_id" : 25,
"floors" : [
{ "floor" : 2,
"persons" : [
{ "name" : "Jhon", "jobTime" : 600, "dept" : 5 },
{ "name" : "Alexa", "jobTime" : 355, "dept" : 6 }
]
}
]
},
{
"_id" : 55,
"floors" : [
{ "floor" : 3,
"persons" : [
{ "name" : "Alice", "jobTime" : 100, "dept" : 3 }
]
},
{ "floor" : 4,
"persons" : [
{ "name" : "Alex", "jobTime" : 841, "dept" : 1 }
]
}
]
},
{
"_id" : 22,
"floors" : [
{ "floor" : 2,
"persons" : [
{ "name" : "Anne", "jobTime" : 280, "dept" : 8 }
]
}
]
}
So the initial $group is on a compound key including the detail down to the items you want to add to the initial "array", for "persons". Then the second $group takes only part of the initial _id for it's key and again "pushes" the content into a new array.

slice in array not working?

This is my mongoose collection data:
{
"ShopId" : "439",
"productName" : "veg",
"productCategory" : "meals",
"mrp" : "38 "
},
{
"ShopId" : "439",
"productName" : "non-veg",
"productCategory" : "meals",
"mrp" : "380 "
},{....}
Query
db.getCollection('ProductDetails').aggregate(
[{ "$match": { "ShopId": "439" } },{"$group": {"_id": "$productCategory", "count": { "$sum": 1 },
"products": {"$push":{"productname": "$productName"}}}},
{"$group": {"_id": null, "productList": {"$push": {"categoryname": "$_id", "productcount": "$count",
"products": "$products"}}}},{$project:{products:{$slice:["$productList.products",2]}}}])
Output:
{
"_id" : null,
"productList" : [
{
"categoryname" : "meals",
"productcount" : 8.0,
"products" : [
{
"productname" : "non veg"
},
{
"productname" : "veg"
},
{
"productname" : "c"
},
{
"productname" : "d"
},
{
"productname" : "df"
},
{
"productname" : "dfr"
},
{
"productname" : "qe"
},
{
"productname" : "as"
}
]
}
]
}
expected output:
I want to limit the number of products to 2.But instead of that all products are displaying.
{
"_id" : null,
"productList" : [
{
"categoryname" : "meals",
"productcount" : 8.0,
"products" : [
{
"productname" : "non veg"
},
{
"productname" : "veg"
}
]
}
]
}
Replace your $project stage with below.
{$project:{products:{$slice:[{$arrayElemAt:["$productList.products", 0]},2]}}}
Your products is array of arrays.
"products": [
[{
"productname": "veg"
}, {
"productname": "non-veg"
}]
]
$arrayElemAt with 0 will pick the inner array and you can use $slice to limit the products.
I believe you are using the $slice function wrong: As I mentioned in this post:
Find a value in array
The slice function takes two parameters: The first is the initial index and the second is the number of elements after this index. Here's an example:
db.collection.find({},{_id:0,products:{$slice : [0,2]})
This will take two elements from the index [0] of the array. Hope my answer was helpful.

Resources