Mongo Compaas filter within Projection - mongodb

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

Related

MongoDB - How to match the value of a field with nested document field value

I have a structure where I want to match the value of a field on root level with the value of a field inside another object in the same document, I got to his structure by unwinding on the nested field. So I have a structure like this:
{
"name": "somename",
"level": "123",
"nested":[
{
"somefield": "test",
"file": {
level:"123"
}
},
{
"somefield": "test2",
"file": {
level:"124"
}
}
]
}
After unwinding I got the structure like:
{
"name": "somename",
"level": "123",
"nested": {
"somefield": "test",
"file": {
level:"123"
}
}
}
So I want to match on level = nested.file.level and return only documents which satisfy this condition.
I tried using
$match: {
"nested.file.level": '$level'
}
also
$project: {
nested: {
$cond: [{
$eq: [
'nested.file.level',
'$level'
]
},
'$nested',
null
]
}
}
Nothing seems to work. Any idea on how I can match based on the mentioned criteria?
Solution 1: With $unwind stage
After $unwind stage, in the $match stage you need to use the $expr operator.
{
$match: {
$expr: {
$eq: [
"$nested.file.level",
"$level"
]
}
}
}
Demo Solution 1 # Mongo Playground
Solution 2: Without $unwind stage
Without $unwind stage, you may work with $filter operator.
db.collection.aggregate([
{
$match: {
$expr: {
$in: [
"$level",
"$nested.file.level"
]
}
}
},
{
$project: {
nested: {
$filter: {
input: "$nested",
cond: {
$eq: [
"$$this.file.level",
"$level"
]
}
}
}
}
}
])
Demo Solution 2 # Mongo Playground

Fetching sum of rows for a type of column value in mongodb as a single output

I am trying to get the sum of field 'score.number' based on the type of a column value work.type in MongoDB. It should fetch sum as 25 for 'hw' ,and 'cw' as 5 as a single output for the student 'A'. Is there a way to achieve it using mongodb queries ? I tried the $group as well but it doesn't seem to fetch the worktype and the sum for each worktype against it for a single student record 'A'.
Expected Output:
after $match you should use $group like this
db.collection.aggregate([
{
$match: {
student: {
$in: [
"A"
]
},
"work.type": {
$in: [
"hw",
"cw"
]
}
}
},
{
"$group": {
"_id": {
"worktype": "$work.type",
"student": "$student"
},
"workScore": {
"$sum": "$score.number"
}
}
}
])
https://mongoplayground.net/p/qzghM5KoAbp
Able to get the sum with these two
$match{
'student': {'$in': ['A']},
"work.type": {'$in': ['hw', 'cw']},
}
followed by
$group
{
_id: '$work.type',
totalAmount: { $sum: "$score.number" },
}
$match {'student': {
$in: [
"A"
]
},
"work.type": {
$in: [
"hw",
"cw"
]
}}
followed by
$group {
"_id": {
"worktype": "$work.type",
"student": "$student"
},
"workScore": {
"$sum": "$score.number"
}
}
followed by
$group {"_id": {
"student": "$_id.student"
},
'list': {'$push': {'worktype':"$_id.worktype", 'workScore': "$workScore" }},
}
Solved output:
Solves the issue.

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 to test each element of array in mongodb subdocument

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

Find({ example: { $elemMatch: { $eq: userId } } }).. in Aggregate - is it possible?

database:
[{to_match: [ userID_1, userId_2 ], data: [{...}] },
{to_match: [ userID_1, userId_2, userId_3 ], data: [{...}] },
{to_match: [ ], data: [{...}] }]
Find by an element in the array 'to-match'.
Current solution:
Replacement.find(
{ applicants: { $elemMatch: { $eq: userId_1 } } },
Aggregate $lookup on the result of 1.
a. Can I Find and Aggregate?
b. Should I first Aggregate and then match ??
if yes, how to match on the element in the array?
I tried Aggregate:
$lookup // OK
{ $match: { applicants: { $in: { userId } } } } // issues
Thank you
Use $lookup and $match in aggregate
Instead of $in use $elemMatch like below:
{ $match: { applicants: { $elemMatch: { $eq: userId_1 } } } }
Doing an $elemMatch on just one field is equivalent to using find(link)
Generally, it is efficient to limit the data that the lookup stage will be working on.
So if I understand correctly, you want to filter the "to_match" array elements and then do a lookup on that result.
Here is what I would suggest:-
aggregate([
{
$project : {
to_match: {
$filter: {
input: "$to_match",
as: "item",
cond: { $eq: [ "$$item", "userId_3" ] }
}
},
data : 1
}
},
{
$match : { "to_match" : {$ne : []}}
},
//// Lookup stage here
])
Based on the field that you want to do a lookup on, you may want to unwind this result.