how to test each element of array in mongodb subdocument - mongodb

I have mongodb document with the following data:
amenities[
{
amenity_id:52,
Amenity:"AC"
},
{
amenity_id:23,
Amenity:"Free Parking"
}
]
I want to match each amenity_id element of the array with particular value and return true or false using condition $cond. I used
"$project":{'Free Parking':{'$cond':{'if':{'$in':['amenities.amenityId',[23]]},'then':'True','else':'False'}}
If a document contains amenity_id= 52 then a query has to return False.
It is returning false irrespective of the menityId. The amenity Id could be list hence using $in. How can i test each element ?

Considering your input collection is
[
{
amenities: [
{
_id: 52,
Amenity: "AC"
},
{
_id: 23,
Amenity: "Free Parking"
}
]
}
]
using aggregate pipeline $map
db.collection.aggregate([
{
$project: {
amenites: {
$map: {
input: "$amenities",
as: "item",
in: {
Amenity: "$$item.Amenity",
_id: "$$item._id",
isValid: {
$cond: {
if: {
$eq: [
"$$item._id",
23
]
},
then: true,
else: false
},
}
}
}
}
}
}
])
You'll get result as:
[
{
"amenites": [
{
"Amenity": "AC",
"_id": 52,
"isValid": false
},
{
"Amenity": "Free Parking",
"_id": 23,
"isValid": true
}
]
}
]

use $unwind operation in mongodb it will unwind the array field in to multiple documents
Please refer this url - https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/
ex:
{
_id :1,
field: [1,2,3]
}
this will be converted to the following using $unwind,
[
{_id, field:1},
{_id, field:2},
{_id, field:3}
]
after $unwind you can run a match query, with $in query,
Ex:
{
$match: {
field: {$in: [1,2]}
}
}

Related

MongoDB - $filter an array of objects with $exists

I have an array of objects and in the $project pipeline of MongoDB I have to choose the one element whose metadata does not exist. So, for example below are a few documents after my $group pipeline -
{
_id: {
genre: "suspense",
},
price: 10210.6,
data: [
{
subGenre: "Thriller",
flag: true,
},
{
subGenre: "jumpScare",
},
{
subGenre: "horror",
flag: true,
}
]
}
After this, I need to run a $project pipeline where I have to only project that element of the data array where the flag does not exist. My syntax for that is -
db.collection.aggregate([
{
"$project": {
"_id": 0,
"price": 1,
"data": {
"$getField": {
"field": "subGenre",
"input": {
"$first": {
"$filter": { input: "$data", cond: { "$exists": [ "$$this.flag", false ] } }
}
}
}
}
}
}
])
But this is throwing an error -
Invalid $project :: caused by :: Unrecognized expression '$exists'
The output should be -
{
price: 10210.6,
subGenre: "jumpScare"
}
Instead of using $exists which is invalid, you can compare the value with:
$eq: [
"$$this.flag",
undefined
]
This aims to check whether the flag field does not exist.
Demo # Mongo Playground
You may also work with checking the $type value is matched with "missing". (Tested this works in MongoDB Compass)
$eq: [
{ $type: "$$this.flag" },
"missing"
]

Mongo Compaas filter within Projection

Recently I am facing a challenege while creating a query in Mongo Compass. Below is the scenario.
I have a set of documents in mongo db like below:
{
_id :1,
'people':[
{
'grade' : ['A','B'],
'stream': [ {
'stream_id: 'CSE',
'stream_name': 'COMPUTER'
},
{
'stream_id: 'ECE',
'stream_name': 'ELECTRONICS'
},
]
},
{
'grade' : ['B'],
'stream': [ {
'stream_id: 'IT',
'stream_name': 'INFORMATION_TECH'
}
]
}
]
}
I need to find the 'PEOPLE' element which has grade as 'A' and stream_name as 'CSE'. So basically I want this output:
{
_id :1,
'people':[
{
'grade' : ['A','B'],
'stream': [ {
'stream_id: 'CSE',
'stream_name': 'COMPUTER'
}
]
]}
I have tried all the $elemMatch features but it's returning the whole document not only that particular index of the array. Please if anyone is aware of mongo compass, let me know.
Mongo is fun it seems :)
You can use aggregations
$unwind to deconstruct the array
$match to get necessary documents, others will be eliminated
$filter to filter the stream, since we get all the documents that passes the condition, we need to filter the stream objects which equal to condition
$group to reconstruct the array that we already deconstructed in first step
here is the code
db.collection.aggregate([
{ $unwind: "$people" },
{
$match: {
$expr: {
$and: [
{ $in: [ "A", "$people.grade" ] },
{ $in: [ "CSE", "$people.stream.stream_id" ] }
]
}
}
},
{
$addFields: {
"people.stream": {
$filter: {
input: "$people.stream",
cond: { $eq: [ "$$this.stream_id", "CSE" ] }
}
}
}
},
{
$group: {
_id: "$_id",
people: { $push: "$people" }
}
}
])
Working Mongo playground

How to use $elemMatch query in mongodb

I tried to filter the data using $elemmatch but it's not correctly working.
My Scenario
Prod Table
[
{
id:1.
product:[
{
id:1,
name:true
},
{
id:2,
name:true
},
{
id:3,
name:false
}
]
}
]
Query
db.Prod.find(
{"product.name": true},
{_id: 0, product: {$elemMatch: {name: true}}});
I got Output
[
{
id:1.
product:[
{
id:1,
name:true
}
]
}
]
Excepted Output
[
{
id:1.
product:[
{
id:1,
name:true
},
{
id:2,
name:true
}
]
}
]
How to achieve this Scenario and I referred this Link Retrieve only the queried element in an object array in MongoDB collection and I tried all the answers in this link mentioned. but Still it's not working can give an example query.
Hmm, you probably didn't try the aggregation provided in referenced link, 'cause it perfectly works.
db.collection.aggregate([
{
$match: {
"product.name": true
}
},
{
$project: {
product: {
$filter: {
input: "$product",
as: "prod",
cond: {
$eq: [
"$$prod.name",
true
]
}
}
}
}
}
])
Will output :
[
{
"_id": 1,
"product": [
{
"id": 1,
"name": true
},
{
"id": 2,
"name": true
}
]
}
]
Here's the example.
EDIT :
From the doc :
Usage Considerations
Both the $ operator and the $elemMatch operator project the first
matching element from an array based on a condition.
Considering this, you cannot achieve what you need with a find query, but only with an aggregation query.

How do you convert an array of ObjectIds into an array of embedded documents with a field containing the original array element value

I have a collection of documents where one of the fields is currently an array of ObjectId items.
{
_id: ObjectId(...),
user: "jdoe",
docs: [
ObjectId(1),
ObjectId(2),
...
]
}
{
_id: ObjectId(...),
user: "jsmith",
docs: [
ObjectId(3),
ObjectId(4),
...
]
}
How can I update all of the documents in my collection to convert the docs field into an array of objects that contain a "docID" field equal to the original element value?
For example, I'd want my documents to end up looking like:
{
_id: ObjectId(...),
user: "jdoe",
docs: [
{ docID: ObjectId(1) },
{ docID: ObjectId(2) },
...
]
}
{
_id: ObjectId(...),
user: "jsmith",
docs: [
{ docID: ObjectId(3)},
{ docID: ObjectId(4)},
...
]
}
I'm hoping there is a command that I can run from the shell such as:
db.getCollection('myCollection').update(
{},
{
$set: {
'docs.$[]: { docID: '$$VALUE'}
}
},
{multi: true }
);
But I can't figure out how to reference the original value of the element.
Update:
I'm marking #mickl with the correct answer since it got me on the correct track. Below is the final aggregate that I ended up with which only changes the docs field if it is an array of object IDs, otherwise the existing value is left as-is, including documents that don't have a docs field.
db.getCollection('myCollection').aggregate([
{ $addFields: {
'docs': { $cond: {
if : { $eq: [{ $type: { $arrayElemAt: [ '$docs', 0]} }, "objectId"]},
then: { $map: {
input: '$docs',
in: { tocID: '$$this'}
}},
else : '$docs'
}}
}},
{ $out: "myCollection" }
])
You can use $map to reshape your data and $out to replace existing collection with aggregation result:
db.col.aggregate([
{
$addFields: {
docs: {
$map: {
input: "$docs",
in: { docID: "$$this" }
}
}
}
},
{ $out: "col" }
])

How to query array elements in mongo so that all nested items pass a given condition?

In Mongo I have data structure like this:
db.elements
{
id: 1,
arr: [
{status: true},
{status: false}
]
},
{
id: 2,
arr: [
{status: true},
{status: true}
]
}
I need to find items where status == true for all elements in arr.
I expect to see item with id==2.
BUT
db.elements.find({'arr.status': true})
will return both
You can perform an aggregate to :
$unwind your array
$group by $_id and by $arr.status
$group by $_id and build an array of all distinct values of status (so it will give you [true,false] or [true] or [false])
$match all records that have the former array size == 1 && first element is true
mongo query is :
db.elements.aggregate([{
"$unwind": "$arr"
}, {
"$group": {
"_id": {
id: "$id",
value: "$arr.status"
}
}
}, {
"$group": {
"_id": "$_id.id",
value: {
$addToSet: '$_id.value'
}
}
}, {
"$match": {
"value": {
"$size": 1,
},
"value.0": true
}
}]);
Here is a demo