$elemMatch for last element instead of first - mongodb

I'm trying to find the last element of an array that matches a condition, for example if I had the data from the $elemMatch page:
{
_id: 1,
students: [
{ name: "john", school: 102, age: 10 },
{ name: "jess", school: 102, age: 11 },
{ name: "jeff", school: 108, age: 15 }
]
}
I want to find the last element that has school: 102 in it, in this case I will get { name: "jess", school: 102, age: 11 }.
But $elemMatch will return john instead.
I feel like there supposed to be an easy way to do it, but couldn't find any solution without using $unwind and complex aggregations.
Any ideas?

You can use $filter to get only students from school 102 and then $slice with -1 to get last element from filtered array. To make it as short as possible you can use $let to define temporary variable, try:
db.col.aggregate([
{
$addFields: {
students: {
$let: {
vars: {
filtered: { $filter: { input: "$students", as: "s", cond: { $eq: [ "$$s.school", 102 ] } } }
},
in: { $slice: [ "$$filtered", -1 ] }
}
}
}
}
])

Related

Field combination in an array where another field is the same in MongoDB?

I want to find matches with the same gender and insert them into a new field array aka names but I am unable to solution using MongoDB. Or mongooese.
Input example:
db.students.insertMany([
{ id: 1, name: "Ryan", gender: "M" },
{ id: 2, name: "Joanna", gender: "F" },
{ id: 3, name: "Andy", gender: "M" },
{ id: 4, name: "Irina", gender: "F" }
]);
Desired output:
[
{ gender: "M", names: ["Ryan","Andy"]},
{ gender: "F", names: ["Joanna","Irina"]}
]
Note: the table has many records and I do not know those gender/name pairs in advance
I try this but no results. I don't know how I should write this query.
db.students.aggregate([
{
$group:{
names : {$push:"$name"},
}
},
{ "$match": { "gender": "$gender" } }
])
You did not specify how to group. Try this one:
db.students.aggregate([
{
$group: {
_id: "$gender",
names: { $push: "$name" }
}
},
{
$set: {
gender: "$_id",
_id: "$$REMOVE"
}
}
])

MongoDB Query: How can I aggregate an array of objects as a string

I have an array of objects where I want to make a string concatenating all of the same attributes from the array. Example:
{
_id: 123,
example_document: true,
people: [
{
name: "John",
age: 18
}, {
name: "Clint",
age: 20
}
]
}
And I wanna make a query where my result would be:
{
_id: 123,
example_document: true,
people: [
{
name: "John",
age: 18
}, {
name: "Clint",
age: 20
}
],
concat_names: "John, Clint"
}
I think aggregate is the path I should take, but I'm not being able to find a way of getting a string out of this, only concat array elements into another array. Anyone could help?
You can use $concat combined with $reduce to achieve this, like so:
db.collection.aggregate([
{
$addFields: {
concat_names: {
$reduce: {
input: "$people",
initialValue: "",
in: {
$concat: [
"$$value",
{
$cond: [
{
$eq: [
{
"$strLenCP": "$$value"
},
0
]
},
"",
", "
]
},
"$$this.name"
]
}
}
}
}
}
])
Mongo Playground
You can use aggregation operators $project, and $map:
db.collection.aggregate([
{
$project:
{
concat_names:
{
$map:
{
input: "$people",
as: "i",
in: "$$i.name"
}
}
}
}])

Querying Array of Embedded Documents in MongoDB based on Range

How can I query array of embedded document in mongodb.
{
_id: 1,
zipcode: "63109",
students: [
{ name: "john", school: 102, age: 14 },
{ name: "jess", school: 102, age: 11 },
{ name: "jeff", school: 108, age: 15 }
]
}
{
_id: 2,
zipcode: "63110",
students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
]
}
{
_id: 3,
zipcode: "63109"
}
Let's say for the above data how will retrieve only those lines where age>=7 and age<=10. For id_1 only the row with age 10 should be returned. Both the rows in id_2 and id_3. Also, id_4 has no field called students. So, I DO NOT, want to see it in the output.
Edit:-
The output I get looks something like this when I do a filter. But I DO NOT want the row that has "None".
The last document _id: 3 does not have the field students but filtering is done on students, so the output has a "None" corresponding to it.
I wish to handle 2 cases:
No filtering should be applied where the array "students" don't exist under an id.
Array students exists but is empty []
Both these cases end up in the output if simply filtered.
{'_id': ObjectId('5cdaefd393436906b016ddb4'),
'students': [{'name': ajax,
'school': '100',
'age': '7'},
{'name': achilles,
'school': '100',
'age': '8'}],
{'_id': ObjectId('5cdaefd393436906b016ddb3'), 'students': None}
Note: I am using a different but identical data set, so the output is not exact, but I hope you get the idea. The 'student' is an embedded document which may or may not be present in all documents.
Edit : Finally I used unwind to achieve what I wanted.
You can use below aggregation
db.collection.aggregate([
{ "$match": { "$expr": { "$gte": [{ "$size": { "$ifNull": ["$students", []] } }, 1] }}},
{ "$addFields": {
"students": {
"$filter": {
"input": { "$ifNull": ["$students", []] },
"cond": {
"$and": [
{ "$gte": ["$$this.age", 7] },
{ "$lte": ["$$this.age", 10] }
]
}
}
}
}}
])

Find max element inside an array

REF: MongoDB Document from array with field value max
Answers in Finding highest value from sub-arrays in documents and MongoDB find by max value in array of documents suggest to use sort + limit(1), however this is really slow. Surely there is a way to use the $max operator.
Suppose one gets a document like this in an aggregate match:
{
_id: "notImportant",
array: [
{
name: "Peter",
age: 17
},
{
name: "Carl",
age: 21
},
{
name: "Ben",
age: 15
}
]
}
And you want to find the (entire, not just the one value) document where age is highest.
How do you do that with the $max operator?
I tried
unwind {"$array"}
project {"_id": 0, "name": "$array.name", "age": "$array.age"}
so I get
{
_id: null,
name: "Peter",
age: 17
}
{
_id: null,
name: "Carl",
age: 21
}
{
_id: null,
name: "Ben",
age: 15
}
Then I tried matching age:
age: {$eq: {$max: "$age"}}
, but that gives me no results.
In other words what I need to get is the name and all other fields that belong to the oldest person in the array. And there are many thousands of persons, with lots of attributes, and on top of it all it runs on a raspberry pi. And I need to do this operation on a few dozen of such arrays. So with the sorting this takes about 40 seconds all in all. So I would really like to not use sort.
can you try this aggregation with $reduce
db.t63.aggregate([
{$addFields : {array : {$reduce : {
input : "$array",
initialValue : {age : 0},
in : {$cond: [{$gte : ["$$this.age", "$$value.age"]},"$$this", "$$value"]}}
}}}
])
output
{ "_id" : "notImportant", "array" : { "name" : "Carl", "age" : 21 } }
If you want all the documents which have the highest value, you should use a filter.
So basically, instead of using those unwind, project, etc, just use the below project stage
$project: {
age: {
$filter: {
input: "$array",
as: "item",
cond: { $eq: ["$$item.age", { $max: "$array.age" }] }
}
}
}
You can use below aggregation
db.collection.aggregate([
{ "$project": {
"max": {
"$arrayElemAt": [
"$array",
{
"$indexOfArray": [
"$array.age",
{ "$max": "$array.age" }
]
}
]
}
}}
])

MongoDB .Need to count fileds of an array

I have a collection like below
{ _id: 1, zipcode: "63109", students: [
{ name: "john", school: 102, age: 10 },
{ name: "jess", school: 102, age: 11 },
{ name: "jeff", school: 108, age: 15 }
] }
{ _id: 2, zipcode: "63110", students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
] }
{ _id: 3, zipcode: "63109", students: [
{ school: 100, age: 7 },
{ school: 100, age: 8 },
] }
{ _id: 4, zipcode: "63109", students: [
{ name: "barney", school: 102, age: 7 },
{ name: "ruth", school: 102, age: 16 },
] }
Note:Some document doesnot have name field.
I want to findout the count of name field.
Kindly suggest query to find count in mongo
You can do this with aggregation framework with $unwind. Let say your collection name is test:
db.test.aggregate(
{$unwind: '$students'},
{$match:{'students.name':{$exists:1}}},
{$group:{_id: '$students.name', count:{$sum:1}}},
{$project:{tmp:{name:'$_id', count:'$count'}}},
{$group:{_id:'Total Names', total:{$sum:1}, data:{$addToSet:'$tmp'}}}
)
Hope this is what you want!
You need to use the aggregation framework in this case.
The first aggregation step would be to use $unwind to turn the arrays into streams of documents.
Then you can use $group as a second aggregation step with $sum:1 to get the count of every name.
db.collection.aggregate([
{
$unwind: "$students"
},
{
$group: {
_id:"$students.name",
count: { $sum:1 }
}
}
]
This is doable through the aggregation framework
I'll assume your collection is called mycoll:
db.mycoll.aggregate([
{$unwind:'$students'},
{$group:{_id:'$name', 'count':{$sum:1}},
{$match:{'students.name':{$exists:1}}}
]);
References:
Aggregations http://docs.mongodb.org/manual/tutorial/aggregation-zip-code-data-set/
Exists: http://docs.mongodb.org/manual/reference/operator/query/exists/
Summing '1' essentially turns it into a count.