MongoDB object property check if $exists with certain value in nested array - node.js

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

Related

MongoDB: Aggregate $lookup b/w 'Customer' and 'Product' collections returns blank array

I have written a small query to get data from two collections,
here is my query as you can see:
Customer.aggregate([
{
$lookup: {
from: "Product",
localField: "product",
foreignField: "_id",
as: "productdata"
}
}
]).exec(function (err, res) {
if (err) {
console.log('Error', err)
}
else {
console.log(res);
}
});
I am always getting below output:
{
_id: 605412b063db104dfcb3d78a,
updated_at: 2021-03-19T02:55:44.098Z,
name: 'bhavesh',
product: '6052c356a76d435cf857aa3c',
city: 'surat',
__v: 0,
productdata: []
},
My collections are as follow:
// Customer collection
{
"_id": ObjectId("6052c3e50016c24b24a37f4d"),
"updated_at": ISODate("2021-03-18T08:37:17.659+05:30"),
"name": "bhavesh",
"product": "6052c356a76d435cf857aa3c",
"city": "surat"
},
{
"_id": ObjectId("6052c40202dce351e8448441"),
"updated_at": ISODate("2021-03-18T08:37:46.184+05:30"),
"name": "alex",
"product": "6052c369b9395c55042373e6",
"city": "ahmdabad"
},
{
"_id": ObjectId("6052c4157d1616563c707732"),
"updated_at": ISODate("2021-03-18T08:38:05.935+05:30"),
"name": "lexa",
"product": "6052c37321b64b4c40ac65a5",
"city": "mumbai"
},
{
"_id": ObjectId("6052c42efa94db04ccf19560"),
"updated_at": ISODate("2021-03-18T08:38:30.411+05:30"),
"name": "bhumi",
"product": "6052c37ef633e64cb8b65423",
"city": "surat"
}
// Product collection
{
"_id": ObjectId("6052c356a76d435cf857aa3c"),
"updated_at": ISODate("2021-03-18T08:34:54.187+05:30"),
"name": "laptop",
"company": "6052c217a1abc325b01ec87c",
"price": 66
},
{
"_id": ObjectId("6052c369b9395c55042373e6"),
"updated_at": ISODate("2021-03-18T08:35:13.939+05:30"),
"name": "charger",
"company": "6052c2311993dc433c0657d7",
"price": 50
},
{
"_id": ObjectId("6052c37321b64b4c40ac65a5"),
"updated_at": ISODate("2021-03-18T08:35:23.503+05:30"),
"name": "tablet",
"company": "6052c2311993dc433c0657d7",
"price": 50
},
{
"_id": ObjectId("6052c37ef633e64cb8b65423"),
"updated_at": ISODate("2021-03-18T08:35:34.979+05:30"),
"name": "bettery",
"company": "6052c217a1abc325b01ec87c",
"price": 500
}
// company collection
{
"_id": ObjectId("6052c217a1abc325b01ec87c"),
"updated_at": ISODate("2021-03-18T08:29:35.387+05:30"),
"name": "artoon",
"status": "active"
},
{
"_id": ObjectId("6052c2311993dc433c0657d7"),
"updated_at": ISODate("2021-03-18T08:30:01.508+05:30"),
"name": "identix",
"status": "active"
}
So basically I want data from 3 collections which have unique relation if you need more description please comment.
The $lookup doesn't match anything is because the localField 'product' of Customer collection is type String and the foreignField _id of Product collection is type ObjectId. The 2 values are not the same even though the look the same from your post. Notice that the _id value doesn't have the single quote around it while other fields (product and company) do.
You need to make them all the same type in order for the search to match. That is making
product field in Customer collection ObjectId type
company field in Product collection ObjectId type
Try this query for performing join between 3 collections:
db.Customer.aggregate([
{
$lookup: {
from: "Product",
let: {
product_id: { $toObjectId: "$product" }
},
pipeline: [
{
$match: {
$expr: { $eq: ["$_id", "$$product_id"] }
}
},
{
$lookup: {
from: "company",
let: {
company_id: { $toObjectId: "$company" },
},
pipeline: [
{
$match: {
$expr: { $eq: ["$_id", "$$company_id"] }
}
}
],
as: "company"
}
},
{ $unwind: "$company" }
],
as: "Product"
}
},
{ $unwind: "$Product" }
]);
Output:
/* 1 createdAt:3/18/2021, 8:37:17 AM*/
{
"_id" : ObjectId("6052c3e50016c24b24a37f4d"),
"updated_at" : ISODate("2021-03-18T08:37:17.659+05:30"),
"name" : "bhavesh",
"product" : "6052c356a76d435cf857aa3c",
"city" : "surat",
"Product" : {
"_id" : ObjectId("6052c356a76d435cf857aa3c"),
"updated_at" : ISODate("2021-03-18T08:34:54.187+05:30"),
"name" : "laptop",
"company" : {
"_id" : ObjectId("6052c217a1abc325b01ec87c"),
"updated_at" : ISODate("2021-03-18T08:29:35.387+05:30"),
"name" : "artoon",
"status" : "active"
},
"price" : 66
}
},
/* 2 createdAt:3/18/2021, 8:37:46 AM*/
{
"_id" : ObjectId("6052c40202dce351e8448441"),
"updated_at" : ISODate("2021-03-18T08:37:46.184+05:30"),
"name" : "alex",
"product" : "6052c369b9395c55042373e6",
"city" : "ahmdabad",
"Product" : {
"_id" : ObjectId("6052c369b9395c55042373e6"),
"updated_at" : ISODate("2021-03-18T08:35:13.939+05:30"),
"name" : "charger",
"company" : {
"_id" : ObjectId("6052c2311993dc433c0657d7"),
"updated_at" : ISODate("2021-03-18T08:30:01.508+05:30"),
"name" : "identix",
"status" : "active"
},
"price" : 50
}
},
/* 3 createdAt:3/18/2021, 8:38:05 AM*/
{
"_id" : ObjectId("6052c4157d1616563c707732"),
"updated_at" : ISODate("2021-03-18T08:38:05.935+05:30"),
"name" : "lexa",
"product" : "6052c37321b64b4c40ac65a5",
"city" : "mumbai",
"Product" : {
"_id" : ObjectId("6052c37321b64b4c40ac65a5"),
"updated_at" : ISODate("2021-03-18T08:35:23.503+05:30"),
"name" : "tablet",
"company" : {
"_id" : ObjectId("6052c2311993dc433c0657d7"),
"updated_at" : ISODate("2021-03-18T08:30:01.508+05:30"),
"name" : "identix",
"status" : "active"
},
"price" : 50
}
},
/* 4 createdAt:3/18/2021, 8:38:30 AM*/
{
"_id" : ObjectId("6052c42efa94db04ccf19560"),
"updated_at" : ISODate("2021-03-18T08:38:30.411+05:30"),
"name" : "bhumi",
"product" : "6052c37ef633e64cb8b65423",
"city" : "surat",
"Product" : {
"_id" : ObjectId("6052c37ef633e64cb8b65423"),
"updated_at" : ISODate("2021-03-18T08:35:34.979+05:30"),
"name" : "bettery",
"company" : {
"_id" : ObjectId("6052c217a1abc325b01ec87c"),
"updated_at" : ISODate("2021-03-18T08:29:35.387+05:30"),
"name" : "artoon",
"status" : "active"
},
"price" : 500
}
}

How to lookup and filter in array of objects mongo?

I have a question about lookup and filter array of objects in mongodb
I have structure: Person
{
"_id": "5cc3366c22c3767a2b114c6b",
"flags": [
"5cc30210fada5d7820d03aaf",
"5cc2c3924a94a575adbdc56a"
],
"key": "Animal",
"name": "name1",
"description": "description1",
"endpoints": [
{
"isEnabled": true,
"publishUrl": "megaUrl",
"env": "5cc1a8911b19026fd193506b"
},
{
"isEnabled": true,
"publishUrl": "megaUrl",
"env": "5ccaeef3312acb103730d4c5"
}
]
}
envs collection
{
"_id" : "5cc1a8911b19026fd193506b",
"name" : "name2",
"key" : "PROD",
"publishUrl" : "url1",
"__v" : 0
}
{
"_id" : "5ccaeef3312acb103730d4c5",
"name" : "name2",
"key" : "PROD",
"publishUrl" : "url1",
"__v" : 0
}
I should filter Document by endpoints.$.env
so, I have: accessKeys = ["PROD", "UAY"], and i should see result . with endpoints where env.key === "PROD" || env.key === "UAT"
Expected result:
{
"_id": "5cc3366c22c3767a2b114c6b",
"flags": [
"5cc30210fada5d7820d03aaf",
"5cc2c3924a94a575adbdc56a"
],
"key": "Animal",
"name": "name1",
"description": "description1",
"endpoints": [
{
"isEnabled": true,
"publishUrl": "megaUrl",
"env": {
"_id" : "5cc1a8911b19026fd193506b",
"name" : "name2",
"key" : "PROD",
"publishUrl" : "url1",
"__v" : 0
}
},
]
}
Help me pls, how i can do that? I know about aggregate, but cant do it :(
Try this :
db.persons.aggregate([{
$unwind : "$endpoints"
},{
$lookup :{
from : "envs",
localField : "endpoints.env",
foreignField : "_id",
as : "endpoints.env"
}
},{
$unwind : "$endpoints.env"
},{
$match : {
"endpoints.env.key" : {$in : accessKeys}
}
},{
$group : {
_id : "$_id",
flags : {$first : "$flags"},
key : {$first : "$key"},
name : {$first : "$name"},
description : {$first : "$description"},
endpoints : {$push : "$endpoints"},
}
}])

Find with arrayFilters using Mongoose

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" ] } ] }

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"
}

Mongo DB + group by with last field data

I have sample json data in collections.
Sample data:
[{
"_id" : 1,
"username" : "abcd",
"createdDate" : ISODate("2016-06-03T08:52:32.434Z")
},
{
"_id" : 2,
"username" : "abcd",
"createdDate" : ISODate("2016-05-03T09:52:32.434Z")
},
{
"_id" : 3,
"username" : "abcd",
"createdDate" : ISODate("2016-04-03T10:52:32.434Z")
},
{
"_id" : 4,
"username" : "xyz",
"createdDate" : ISODate("2016-03-03T10:52:32.434Z")
},{
"_id" : 5,
"username" : "xyz",
"createdDate" : ISODate("2016-02-03T10:52:32.434Z")
},{
"_id" : 6,
"username" : "zzz",
"createdDate" : ISODate("2016-01-03T10:52:32.434Z")
}]
This data I need to retrieve data for following condtions.
Group by username.
username not equal "zzz"
Order by date desc order.
need date field also (which have lastest/last record).
get total count.
Expecting output:
[{
"username" : "abcd",
"createdDate" : "2016-06-03T08:52:32.434Z",
"total" : 3
},
{
"username" : "xyz",
"createdDate" : "2016-03-03T10:52:32.434Z",
"total" : 2
}]
Query:
db.logs.aggregate([
{ "$match": { "username": { "$ne": "zzz" } }},
{ "$group": {
"_id": {
"username": "$username",
"createdDate": "$createdDate"
},
"count": { "$sum": 1 }
}}])
try this :
db.logs.aggregate([
{
"$match":{
"username":{
"$ne":"zzz"
}
}
},
{
"$group":{
_id:"$username",
"count":{
"$sum":1
},
date:{
$max:"$createdDate"
}
}
},
{
$project:{
username:"$_id",
total:"$count",
createdDate:"$date"
}
}
])
output
{
"_id":"xyz",
"username":"xyz",
"total":2,
"createdDate": ISODate("2016-03-03T10:52:32.434 Z")
}{
"_id":"abcd",
"username":"abcd",
"total":3,
"createdDate": ISODate("2016-06-03T08:52:32.434 Z")
}
try it online: mongoplayground.net/p/3_-s2tUjPFi

Resources