How to find a document with maximum field value in mongodb? - mongodb

I have a number of Mongodb documents of the following form:
{
"auditedId" : "53d0f648e4b064e8d746b31c",
"modifications" : [
{
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31d"),
"modified" : "2014-07-22 18:33:05"
},
{
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31e"),
"modified" : "2014-07-24 14:15:27"
},
{
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31f"),
"modified" : "2014-07-24 12:04:24"
}
]
}
For each of these documents I want to find "auditRecordId" value which corresponds to the latest modification. In the given example I want to retrieve
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31e")
Or, even better:
{
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31e"),
"modified" : "2014-07-24 14:15:27"
}
Is there any way how I can do this without writing map-reduce functions?

Whenever you have an array in your document, the aggregate method is your friend :)
db.foo.aggregate([
// De-normalize the 'modifications' array
{"$unwind":"$modifications"},
// Sort by 'modifications.modified' descending
{"$sort":{"modifications.modified":-1}},
// Pick the first one i.e., the max
{"$limit":1}
])
Output:
{
"result" : [
{
"_id" : ObjectId("53d12be57a462c7459b6f1c7"),
"auditedId" : "53d0f648e4b064e8d746b31c",
"modifications" : {
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31e"),
"modified" : "2014-07-24 14:15:27"
}
}
],
"ok" : 1
}
Just to illustrate the $unwind operator, I used the above query with $limit. If you have multiple documents of the above format, and you want to retrieve the latest modification in each, you'll have to add another $group phase in your aggregation pipeline and use the $first operator:
db.foo.aggregate([
{"$unwind":"$modifications"},
{"$sort":{"modifications.modified":-1}},
{"$group":{
"_id" : "$auditedId",
"modifications" : {$first:"$modifications"}}}
])

Related

MongoDB Sorting: Equivalent Aggregation Query

I have following students collection
{ "_id" : ObjectId("5f282eb2c5891296d8824130"), "name" : "Rajib", "mark" : "1000" }
{ "_id" : ObjectId("5f282eb2c5891296d8824131"), "name" : "Rahul", "mark" : "1200" }
{ "_id" : ObjectId("5f282eb2c5891296d8824132"), "name" : "Manoj", "mark" : "1000" }
{ "_id" : ObjectId("5f282eb2c5891296d8824133"), "name" : "Saroj", "mark" : "1400" }
My requirement is to sort the collection basing on 'mark' field in descending order. But it should not display 'mark' field in final result. Result should come as:
{ "name" : "Saroj" }
{ "name" : "Rahul" }
{ "name" : "Rajib" }
{ "name" : "Manoj" }
Following query I tried and it works fine.
db.students.find({},{"_id":0,"name":1}).sort({"mark":-1})
My MongoDB version is v4.2.8. Now question is what is the equivalent Aggregation Query of the above query. I tried following two queries. But both didn't give me desired result.
db.students.aggregate([{"$project":{"name":1,"_id":0}},{"$sort":{"mark":-1}}])
db.students.aggregate([{"$project":{"name":1,"_id":0,"mark":1}},{"$sort":{"mark":-1}}])
Why it is working in find()?
As per Cursor.Sort, When a set of results are both sorted and projected, the MongoDB query engine will always apply the sorting first.
Why it isn't working in aggregate()?
As per Aggregation Pipeline, The MongoDB aggregation pipeline consists of stages. Each stage transforms the documents as they pass through the pipeline. Pipeline stages do not need to produce one output document for every input document; e.g., some stages may generate new documents or filter out documents.
You need to correct:
You should change pipeline order, because if you have not selected mark field in $project then it will no longer available in further pipelines and it will not affect $sort operation.
db.students.aggregate([
{ "$sort": { "mark": -1 } },
{ "$project": { "name": 1, "_id": 0 } }
])
Playground: https://mongoplayground.net/p/xtgGl8AReeH

MongoDB get all embedded documents where condition is met

I did this in my mongodb:
db.teams.insert({name:"Alpha team",employees:[{name:"john"},{name:"david"}]});
db.teams.insert({name:"True team",employees:[{name:"oliver"},{name:"sam"}]});
db.teams.insert({name:"Blue team",employees:[{name:"jane"},{name:"raji"}]});
db.teams.find({"employees.name":/.*o.*/});
But what I got was:
{ "_id" : ObjectId("5ddf3ca83c182cc5354a15dd"), "name" : "Alpha team", "employees" : [ { "name" : "john" }, { "name" : "david" } ] }
{ "_id" : ObjectId("5ddf3ca93c182cc5354a15de"), "name" : "True team", "employees" : [ { "name" : "oliver" }, { "name" : "sam" } ] }
But what I really want is
[{"name":"john"},{"name":"oliver"}]
I'm having a hard time finding examples of this without using some kind of programmatic iterator/loop. Or examples I find return the parent document, which means I'd have to parse out the embedded array employees and do some kind of UNION statement?
Eg.
How to get embedded document in mongodb?
Retrieve only the queried element in an object array in MongoDB collection
Can someone point me in the right direction?
Please add projections to filter out the fields you don't need. Please refer the project link mongodb projections
Your find query should be constructed with the projection parameters like below:
db.teams.find({"employees.name":/.*o.*/}, {_id:0, "employees.name": 1});
This will return you:
[{"name":"john"},{"name":"oliver"}]
Can be solved with a simple aggregation pipeline.
db.teams.aggregate([
{$unwind : "$employees"},
{$match : {"employees.name":/.*o.*/}},
])
EDIT:
OP Wants to skip the parent fields. Modified query:
db.teams.aggregate([
{$unwind : "$employees"},
{$match : {"employees.name":/.*o.*/}},
{$project : {"name":"$employees.name",_id:0}}
])
Output:
{ "name" : "john" }
{ "name" : "oliver" }

Return flattened array from each element in a nested array mongo with aggregation query

I have a collection setup with documents that look like :
{
"_id" : ObjectId("5c786d9486c1140b1452d777"),
"code" : "TEST-123",
"owner" : "John",
"cars" : [
{
"carPlate" : "QPZ-756",
"carColor" : "blue"
},
{
"carPlate" : "REF-473",
"carColor" : "red"
}
],
}
I'm looking for an mongo aggregate query that grabs each carPlate and outputs the following for every document in the collection
{
"carPlate" : "QPZ-756",
"owner" : "John",
"code" : "TEST-123",
},
{
"carPlate" : "REF-473",
"owner" : "John",
"code" : "TEST-123",
},
I had a look at the $map operator, would this be a good place to start?
I would use $unwind to flatten the array followed by $mergeObjects to combine keys along with $replaceRoot to promote the merge documents to the top.
Something like
db.colname.aggregate([
{$unwind:"$cars"},
{$replaceRoot:{newRoot:{$mergeObjects:[{owner:"$owner"}, "$cars"]}}}
])

Mongodb unwind and match VS match and unwind

I'm looking to optimize the MongoDB performance by minimizing the number of records to unwind.
I do like:
unwind(injectionRecords),
match("machineID" : "machine1"),
count(counter)
But because of huge data unwind operation takes a lot of time and then it matches from unwind.
It unwinds all the 4 records then matches machineID from result and give me count of it.
Instead I would like to do something like :
match("machineID": "machine1"),
unwind(injectionRecords)
count(counter)
So, it would match records having machineID and unwind only 2 instead of 4 and give me the count of it.
Is this possible? How can I do this?
Here are sample docs,
{
"_id" : ObjectId("5981c24b90a7c215e4f166dd"),
"machineID" : "machine1",
"injectionRecords" : [
{
"startTime" : ISODate("2017-08-02T17:45:04.779+05:30"),
"endTime" : ISODate("2017-08-02T17:45:07.763+05:30"),
"counter" : 1
},
{
"startTime" : ISODate("2017-08-02T17:45:24.417+05:30"),
"endTime" : ISODate("2017-08-02T17:45:27.402+05:30"),
"counter" : 2
}
]
},
{
"_id" : ObjectId("5981c24b90a7c215e4f166de"),
"machineID" : "machine2",
"injectionRecords" : [
{
"startTime" : ISODate("2017-08-02T17:46:04.779+05:30"),
"endTime" : ISODate("2017-08-02T17:46:07.763+05:30"),
"counter" : 1
},
{
"startTime" : ISODate("2017-08-02T17:46:24.417+05:30"),
"endTime" : ISODate("2017-08-02T17:46:27.402+05:30"),
"counter" : 2
}
]
}
The following query will return a count of injectionRecords for a given machineId. I think this is what you are asking for.
db.collection.aggregate([
{$match: {machineID: 'machine1'}},
{$unwind: '$injectionRecords'},
{$group:{_id: "$_id",count:{$sum:1}}}
])
Of course, this query (where the unwind takes place before the match) is functionally equivalent:
db.collection.aggregate([
{$unwind: '$injectionRecords'},
{$match: {machineID: 'machine1'}},
{$group:{_id: "$_id",count:{$sum:1}}}
])
However, running that query with explain ...
db.collection.aggregate([
{$unwind: '$injectionRecords'},
{$match: {machineID: 'machine1'}},
{$group:{_id: "$_id",count:{$sum:1}}}
], {explain: true})
... shows that the unwind stage applies to the entire collection whereas if you match before unwinding then only the matched documents are unwound.

Return latest record from subdocument in Mongodb

Let's say i want to return the latest inserted document from the subdocument. I want to be able to return the second record within the tags array w/ the _id of 54a1845def7572cd0e3fe288
So I far I have this query but it returns all values in the tags array.
db.modules.findOne({_id:"ui","svn_branches.branch":"Rocky"},{"svn_branches.$":1})
Mongodb array:
{
"_id" : "ui",
"svn_branches" : [
{
"updated_at" : ISODate("2013-06-12T20:48:17.297Z"),
"branch" : "Rocky",
"revision" : 0,
"tags" : [
{
"_id" : ObjectId("54a178b8ef7572d30e3fe288"),
"commit_message" : "r277 | ssmith | 2015-02-11 17:43:23 -0400 (Wed, 11 Feb 2015)",
"latest_tag" : "20150218r1_6.32_abc",
"revision" : 1,
"tag_revision_number" : "280",
"updated_at" : ISODate("2015-02-18T19:54:54.062Z")
},
{
"_id" : ObjectId("54a1845def7572cd0e3fe288"),
"commit_message" : "r271 | sam | 2dskjh\n",
"latest_tag" : "20150218r2_6.32_abc",
"revision" : 2,
"tag_revision_number" : "281",
"updated_at" : ISODate("2015-02-19T19:54:54.062Z")
}
]
}
]
}
Simple Solution
Let say we have a category as a document and items as a subdocument.
// find document from collection
const category = await Category.findOne({ _id:'$hec453d235xhHe4Y' });
// fetch last index of sub-document
const lastItemIndex = category.items.length - 1;
// here is the last item of sub-document
console.log(category.items[lastItemIndex]);
as mongodb inserted the latest sub-document at last index, so we need to find the last index for the latest sub-doc.
Queries in MongoDB do not return subdocuments (or, as in your case, subdocuments of subdocuments). They match and return the the documents in the collection. The documents' shape can be changed a bit by projection, but it's limited. If you want to find the latest tag commonly, you probably want to make your documents represent tags. Having an array in an array is generally a bad idea in MongoDB, too.
If this is an uncommon operation, and one that doesn't need to be particularly fast, you can use an aggregation:
db.modules.aggregate([
{ "$unwind" : "$svn_branches" },
{ "$unwind" : "$svn_branches.tags" },
{ "$sort" : { "svn_branches.tags.updated_at" : -1 } },
{ "$group" : { "_id" : "$_id", "latest_tag" : { "$first" : "$svn_branches.tags" } } }
])
I needed to find the last entry of subdocuments and I managed to make it to work with the $slice projection operator: mondodb.com > $slice (projection)
db.modules.find({_id:'ui', 'svn_branches.branch':'Rocky'},
{ 'svn_branches.tags': {$slice:-1} } )
I had only one level, if this doesn't work, please let me know.