How to match an element in array? MongoDB aggregation - mongodb

I need to check all the documents whose specific field is contained in an array.
For example I have the array
arr = ['a', 'b', 'a']
I want to match all the documents that has field my_letter equal to a or b.
I have the documents:
[
{
_id: ObjectID(),
my_letter:'d'
},
{
_id: ObjectID(),
my_letter:'a'
},
{
_id: ObjectID(),
my_letter:'b'
}
]
I want the aggregation to return
[
{
_id: ObjectID(),
my_letter:'a'
},
{
_id: ObjectID(),
my_letter:'b'
}
]
I tried this in my $match pipeline
{
$match: {
_id: {
$elemMatch: {
$or: [
{ $eq: ["a"] },
{ $eq: ["b"] },
],
},
},
},
},
Of course it doesn't work. How would You suggest to complete the $match pipeline?

db.collection.find({
my_letter: {
$in: [ "a", "b" ]
}
})
mongoplayground
db.collection.aggregate([
{
$match: {
my_letter: {
$in: [ "a", "b" ]
}
}
}
])
mongoplayground

Related

Remove Some array elements based on a condition and update size of array as one more filed in mongo

I have following collection
[
{
"_id": ObjectId("57315ba4846dd82425ca2408"),
"myarray": [
{
"point": 5,
"userId": "570ca5e48dbe673802c2d035"
},
{
"point": 2,
"userId": "613ca5e48dbe673802c2d521"
},
{
"point": 4,
"userId": "570ca5e48dbe673802c2d045"
},
{
"point": 4,
"userId": "570ca5e48dbe473802c2d035"
}
]
}
]
I have a collection like above and I want to remove some objects inside array based on userID condition and after removing I have to update one field in mongo with size of array
I'm trying with the below query where removing array elements is working as excepted but array size is not updating properly
db.collection.update({
_id: ObjectId("57315ba4846dd82425ca2408")
},
{
$pull: {
"myarray": {
userId: {
$in: [
"570ca5e48dbe673802c2d035",
"613ca5e48dbe673802c2d521"
]
}
}
},
"$set": {
profilecount: {
$size: "$myarray"
}
}
})
to see result of query please click this link and run query https://mongoplayground.net/p/FtMk7ymacr3
One option is using an update with a pipeline:
db.collection.update({
_id: ObjectId("57315ba4846dd82425ca2408")
},
[{
$set: {
"myarray": {
$filter: {
input: "$myarray",
cond: {
$not: {
$in: [
"$$this.userId",
["570ca5e48dbe673802c2d035", "613ca5e48dbe673802c2d521"]
]
}
}
}
}
}
},
{$set: {profilecount: {$size: "$myarray"}}}
])
See how it works on the playground example

mongodb join collection without nested result

I have two collections on which I want to do a query. Based on some condition it should return the results from both the collections in separate objects. I have looked for online solutions but didn't find anything.
sample
col1: [
{
_id: "st_123",
stud_num: 123,
school: "sc_123"
},
{
_id: "st_234",
stud_num: 123,
school: "sc_234"
},
{
_id: "st_345",
stud_num: 123,
school: "sc_345"
}
]
col2: [
{
_id: "f_123",
stud_health_id: "st_123",
schoolYear: "sy123",
fk_school: "sc_123"
},
{
_id: "f_234",
stud_health_id: "st_234",
schoolYear: "sy234",
fk_school: "sc_234"
},
{
_id: "f_345",
stud_health_id: "st_234",
schoolYear: "sy234",
fk_school: "sc_237"
}
]
When I send some filter like { std_id: 'st_123', school_id: 'sc_123' }, it should return this.
[
{
_id: "st_123",
stud_num: 123,
school: "sc_123"
},
{
_id: "f_123",
stud_health_id: "st_123",
schoolYear: "sy123",
fk_school: "sc_123"
},
]
The request param std_id matches with col1 _id and col2 stud_health_id. The school_id matches with col1 school and col2 fk_school. The objective of this is to merge the two so it can be also paginated. Is there any way this can be done with aggregation. Both of the collections will have thousands of record. I have tried lookup but that returns a nested array inside of col1 documents.
You can try this one:
db.collection.aggregate([
{
"$project": {
a: {
"$concatArrays": [
"$col1",
"$col2"
]
}
}
},
{
$project: {
a: {
$filter: {
input: "$a",
cond: {
$and: [
{
$eq: [
"$$this._id",
"st_123"
]
},
{
$eq: [
"$$this.school",
"sc_123"
]
}
]
}
}
}
}
},
{
"$unwind": "$a"
},
{
$replaceRoot: {
newRoot: "$a"
}
}
])
https://mongoplayground.net/p/rRzubuZXW8W

How to properly reference nested field within an array within an aggregate framework (MongoDB)?

I have a collection in the following format:
Collection name: COLL1
{
_id: "a",
list: [
{
_id: "a1",
ranking: 10
},
{
_id: "a2",
ranking: 30
}
...
]
}
{
_id: "b",
list: [
{
_id: "b1",
ranking: 10
},
{
_id: "b2",
ranking: 30
}
...
]
}
When I call: db.getCollection('COLL1').find({"_id": "a","list._id": "a1"}); I can see the results. However, if I call:
db.getCollection('COLL1').aggregate([
{ $match:
{ $expr:
{ $and:
[
{ $eq: ["$_id", "a"] },
{ $eq: ["$list._id", "a1"] }
]
}
}
}
])
Then nothing is returned. Does anyone know why? I think the issue is { $eq: ["$list._id", "a1"] } but I'm not sure what exactly happened here.
I'm trying to get the complete document:
{
_id: "a",
list: [
{
_id: "a1",
ranking: 10
},
{
_id: "a2",
ranking: 30
}
...
]
}
This is part of my aggregate syntax within a $lookup stage, so I have to use aggregate([]) instead of find(). What I'm actually trying to achieve is the following:
...previous stages
{
$lookup:{
from: "COLL1",
let: { local_id: "$_id" }, // this '$_id' is from another collection, not COLL1.
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$_id", "a"] },
{ $eq: [ "$$local_id", "$list._id" ] } // this is where I got in trouble.
]
}
}
}
],
as: "matched_result"
}
},
Thanks a lot!
If you don't need variables from previous pipeline stages, you can just use the same syntax as .find() inside $match
db.getCollection('COLL1').aggregate([
{ $match: { "_id": "a", "list._id": "a1" } }
])
Replacing the second $eq with $in will work just fine. :)
db.getCollection('COLL1').aggregate([
{ $match:
{ $expr:
{ $and:
[
{ $eq: ["$_id", "a"] },
{ $in: ["a1", "$list._id"] }
]
}
}
}
])

Mongodb - aggregation $push if conditional

I am trying to aggregate a batch of documents. There are two fields in the documents I would like to $push. However, lets say they are "_id" and "A" fields, I only want $push "_id" and "A" if "A" is $gt 0.
I tried two approaches.
First one.
db.collection.aggregate([{
"$group":{
"field": {
"$push": {
"$cond":[
{"$gt":["$A", 0]},
{"id": "$_id", "A":"$A"},
null
]
}
},
"secondField":{"$push":"$B"}
}])
But this will push a null value to "field" and I don't want it.
Second one.
db.collection.aggregate([{
"$group":
"field": {
"$cond":[
{"$gt",["$A", 0]},
{"$push": {"id":"$_id", "A":"$A"}},
null
]
},
"secondField":{"$push":"$B"}
}])
The second one simply doesn't work...
Is there a way to skip the $push in else case?
ADDED:
Expected documents:
{
"_id":objectid(1),
"A":2,
"B":"One"
},
{
"_id":objectid(2),
"A":3,
"B":"Two"
},
{
"_id":objectid(3),
"B":"Three"
}
Expected Output:
{
"field":[
{
"A":"2",
"_id":objectid(1)
},
{
"A":"3",
"_id":objectid(2)
},
],
"secondField":["One", "Two", "Three"]
}
You can use "$$REMOVE":
This system variable was added in version 3.6 (mongodb docs)
db.collection.aggregate([{
$group:{
field: {
$push: {
$cond:[
{ $gt: ["$A", 0] },
{ id: "$_id", A:"$A" },
"$$REMOVE"
]
}
},
secondField:{ $push: "$B" }
}
])
In this way you don't have to filter nulls.
This is my answer to the question after reading the post suggested by #Veeram
db.collection.aggregate([{
"$group":{
"field": {
"$push": {
"$cond":[
{"$gt":["$A", 0]},
{"id": "$_id", "A":"$A"},
null
]
}
},
"secondField":{"$push":"$B"}
},
{
"$project": {
"A":{"$setDifference":["$A", [null]]},
"B":"$B"
}
}])
One more option is to use $filter operator:
db.collection.aggregate([
{
$group : {
_id: null,
field: { $push: { id: "$_id", A : "$A"}},
secondField:{ $push: "$B" }
}
},
{
$project: {
field: {
$filter: {
input: "$field",
as: "item",
cond: { $gt: [ "$$item.A", 0 ] }
}
},
secondField: "$secondField"
}
}])
On first step you combine your array and filter them on second step
$group: {
_id: '$_id',
tasks: {
$addToSet: {
$cond: {
if: {
$eq: [
{
$ifNull: ['$tasks.id', ''],
},
'',
],
},
then: '$$REMOVE',
else: {
id: '$tasks.id',
description: '$tasks.description',
assignee: {
$cond: {
if: {
$eq: [
{
$ifNull: ['$tasks.assignee._id', ''],
},
'',
],
},
then: undefined,
else: {
id: '$tasks.assignee._id',
name: '$tasks.assignee.name',
thumbnail: '$tasks.assignee.thumbnail',
status: '$tasks.assignee.status',
},
},
},
},
},
},
},
}

mongodb - Filter object where all elements from nested array match the condition

Supose a database containing something like that
{
"grades":[
{
"grade":"A",
"score":2
},
{
"grade":"A",
"score":6
},
],
"name":"Morris Park Bake Shop"
},
{
"grades":[
{
"grade":"A",
"score":8
},
{
"grade":"B",
"score":23
}
],
"name":"Wendy'S"
}
How can I apply a filter that will just return the restaurants where ALL grades are "A"?
If I try
db.restaurants.find({ "grades.grade" : "A" } ), the way it works is that it search for ANY grade inside my element.
I tried using aggregate with unwind to, but it do the same thing, it opens grades, filter, and returns any match of restaurant...
In your situation I would do something like this :
db.getCollection('test').aggregate([
{$unwind:"$grades"},
{ $group: {
_id: '$_id',
grades : { $first: '$grades' },
all_grades: { $sum: 1 },
all_grades_that_match: { $sum: { $cond: [ { $eq: [ '$grades.grade', "A" ] }, 1, 0 ] } },
name: { $first: '$name' }
}},
{ $project: {
_id: 1,
name: 1,
grades: 1,
arrays_equal: { $cond: [ { $eq: [ '$all_grades', '$all_grades_that_match' ] }, 1, 0 ] }
}},
{ $match: { 'arrays_equal' : 1 } }
])
The group operation will count the total number of grades and the number of grades that match you query, the projection will compare those two results to see if they are equal, finally, the match operation will only keep the ones where arrays_equal is true