Group by array of document in Spring Mongo Db - mongodb

How can I group by tagValue in Spring and MongoDb?
MongoDB Query :
db.feed.aggregate([
{ $group: { _id: "$feedTag.tagValue", number: { $sum : 1 } } },
{ $sort: { _id : 1 } }
])
How can I do the same thing in Spring MongoDB, may be using Aggregation method?
Sample document of feed collections:
{
"_id" : ObjectId("556846dd1df42d5d579362fd"),
"feedTag" : [
{
"tagName" : "sentiment",
"tagValue" : "neutral",
"modelName" : "sentiment"
}
],
"createdDate" : "2015-05-28"
}

To group by tagValue, since this is an array field, you need to apply the $unwind pipeline step before the group to split the array so that you can get the actual count:
db.feed.aggregate([
{
"$unwind": "$feedTag"
}
{
"$group": {
"_id": "$feedTag.tagValue",
"number": { "$sum" : 1 }
}
},
{ "$sort": { "_id" : 1 } }
])
The following is the equivalent example in Spring Data MongoDB:
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
Aggregation agg = newAggregation(
unwind("feedTag"),
group("feedTag.tagValue").count().as("number"),
sort(ASC, "_id")
);
// Convert the aggregation result into a List
AggregationResults<Feed> results = mongoTemplate.aggregate(agg, "feed", Feed.class);
List<Feed> feedCount = results.getMappedResults();
From the above, a new aggregation object is created via the newAggregation static factory method which is passed a list of aggregation operations that define the aggregation pipeline of your Aggregation.
The firt step uses the unwind operation to generate a new document for each tag within the "feedTag" array.
In the second step the group operation defines a group for each embedded "feedTag.tagValue"-value for which the occurrence count is aggregated via the count aggregation operator.
As the third step, sort the resulting list of feedTag by their tagValue in ascending order via the sort operation.
Finally call the aggregate Method on the MongoTemplate to let MongoDB perform the actual aggregation operation with the created Aggregation as an argument.
Note that the input collection is explicitly specified as the "feed" parameter to the aggregate Method. If the name of the input collection is not specified explicitly, it is derived from the input-class passed as first parameter to the newAggreation Method.

Related

How to count the number of documents with the specified field in MongoDB?

Consider the below documents in a collection "project"
{
_id:"1",
"project_id":"1",
"Name":"A",
"type":"Description"
}
{
_id:"2",
"Name":"A",
"project_id":"2",
"type":"Paragraph"
}
{
_id:"3",
"Name":"A",
"project_id":"1",
"type":"Description"
}
{
_id:"4",
"Name":"A",
"project_id":"3",
"type":"Description"
}
I want to write a mongodb query where it has to count the number of documents with "type":"Description" for the "project_id":"1".
You can find the count by using any of the following methods:
Using db.collection.countDocuments(query, options)
db.collection.countDocuments({ "project_id": "1","type":"Description"})
It performs an aggregation of the document to return an accurate count
Using db.collection.count(query, options)
db.collection.count({ "project_id": "1","type": "Description" })
Avoid using the db.collection.count() method without a query predicate since without the query predicate, the method returns results based on the collection’s metadata, which may result in an approximate count.
Using aggregation pipeline stage $count
db.collection.aggregate([
{
$match: {
"project_id": "1",
"type": "Description"
}
},
{
$count: "count"
}
])
MongoDB Query
PS: Replace the "collection" with your collection name.

Get record having highest date inside nested group in Mongodb

I am having a record set like below :
I need to write a query where foreach datatype of every parent I show the data type with highest date i.e
So far I am able to create two groups one on parent id & other on data type but i am unable to understand how to get record with max date.
Below is my query :
db.getCollection('Maintenance').aggregate( [{ $group :
{ _id :{ parentName: "$ParentID" , maintainancename : "$DataType" }}},
{ $group : {
_id : "$_id.parentName",
maintainancename: {
$push: {
term:"$_id.DataType"
}
}
}
}] )
You don't have to $group twice, try below aggregation query :
db.collection.aggregate([
/** group on two fields `ParentID` & `Datatype`,
* which will leave docs with unique `ParentID + Datatype`
* & use `$max` to get max value on `Date` field in unique set of docs */
{
$group: {
_id: {
parentName: "$ParentID",
maintainancename: "$Datatype"
},
"Date": { $max: "$Date" }
}
}
])
Test : mongoplayground
Note : After group stage you can use $project or $addFieldsstages to transform fields the way you want.

How to apply group by on nested document in MongoDB using MongoTemplate?

db.students.aggregate([
{ $unwind: "$details" },
{
$group: {
_id: {
sid: "$details.student._id",
statuscode: "$details.studentStatus.statusCode"
},
total: { $sum: 1 }
}
}
]);
The query is working fine and need to convert into mongo template.
Sample document:
{
"_id" : 59,
"details" : [
{
"student" : {
"_id" : "5d3145a8523a2e602e5e0200"
},
"studentStatus" : {
"statusCode" : 1
}
}
]
}
The Spring Data MongoTemplate code for the given aggregation is as follows.
Note that I have added a project stage before the group. This project is required; if the nested fields ("details.student._id" and "details.studentStatus.statusCode") are used directly within the group stage there are errors "FieldPath field names may not contain '.'." and could not be resolved (and this only happens when you use more than one field in the grouping).
The result is same as that of the aggregation you have provided. I have used the latest of Spring and MongoDB drivers with Java 8.
MongoOperations mongoOps = new MongoTemplate(MongoClients.create(), "spr_test");
Aggregation agg = newAggregation(
unwind("details"),
project("_id")
.and("details.student._id").as("sid")
.and("details.studentStatus.statusCode").as("statuscode"),
group("sid", "statuscode")
.count().as("total")
);
AggregationResults<Document> aggResults = mongoOps.aggregate(agg, "students", Document.class);
aggResults.forEach(System.out::println);

Save length of array in extra MongoDB field [duplicate]

This question already has answers here:
MongoDB: count the number of items in an array
(3 answers)
Closed 5 years ago.
According to this answer, I am trying to find out the size of an array and save it in the extra field.
I have a collection user_details and the document structure is something like :
{
user_id : 1,
likes : [1,2,3,4],
likes_count : 0
}
The query I'm using is as follows :
db.user_details.update({user_id : 1},{$set:{ likes_count : this.likes.length }})
BUT, it throws error that
"message" : "Cannot read property 'length' of undefined"
How do I save the length of an array in extra field?
PS: I'm using MongoDB 3.4
With MongoDB 3.4 and newer, you can use the $addFields pipeline to add the desired field ($addFields stage is equivalent to a $project stage that explicitly specifies all existing fields in the input documents and adds the new fields) and then write the result of the aggregate operation to the same collection using $out operator, thereby effectively updating the underlying collection.
If the collection specified by the $out operation already exists, then upon completion of the aggregation, the $out stage atomically replaces the existing collection with the new results collection.
To get the count, use the $size operator which returns the length of an array field. Bear in mind that all documents must have the likes field.
Overall, you need to run the following pipeline to achieve the desired update operation:
db.user_details.aggregate([
{ "$addFields": { "likes_count": { "$size": "$likes" } } },
{ "$out": "user_details" }
])
https://docs.mongodb.com/manual/reference/operator/aggregation/size/#exp._S_size
db.users.aggregate(
[
{
$project: {
likes_count: { $size: "$test" }
}
}
]
)
store the returned likes_count in an variable and perform update by providing the likes_count variable
something like this
Model.aggregate(
[
{
$project: {
likes_count: { $size: "$test" }
}
}
], (err, re) => {
console.log(err, re);
var likes_count = re[0].likes_count;
Model.update({email: 1}, {$set: {likes_count: likes_count}}, (err, d) => {
console.log(err, d);
})
}
)

MongoDB's Aggregation Framework: project only matching element of an array

I have a "class" document as:
{
className: "AAA",
students: [
{name:"An", age:"13"},
{name:"Hao", age:"13"},
{name:"John", age:"14"},
{name:"Hung", age:"12"}
]
}
And i want to get the student who has name is "An", get only matching element in array "students". I can do that with function find() as:
>db.class.find({"students.name":"An"}, {"students.$":true})
{
"_id" : ObjectId("548b01815a06570735b946c1"),
"students" : [
{
"name" : "An",
"age" : "13"
}
]}
It's fine, but when i do the same with Aggregation as following, it get error:
db.class.aggregate([
{$match:{"students.name":'An'}},
{$project:{"students.$":true}}
])
Error is:
uncaught exception: aggregate failed: {
"errmsg" : "exception: FieldPath field names may not start with '$'.",
"code" : 16410,
"ok" : 0
}
Why? I can't use "$" for array in $project operator of aggregate() while can use this one in project operator of find().
From the docs:
Use $ in the projection document of the find() method or the findOne()
method when you only need one particular array element in selected
documents.
The positional operator $ cannot be used in an aggregation pipeline projection stage. It is not recognized there.
This makes sense, because, when you execute a projection along with a find query, the input to the projection part of the query is a single document that has matched the query.The context of the match is known even during projection. So for each document that matches the query, the projection operator is applied then and there before the next match is found.
db.class.find({"students.name":"An"}, {"students.$":true})
In case of:
db.class.aggregate([
{$match:{"students.name":'An'}},
{$project:{"students.$":true}}
])
The aggregation pipeline is a set of stages. Each stage is completely unaware and independent of its previous or next stages. A set of documents pass a stage completely before being passed on to the next stage in the pipeline. The first stage in this case being the $match stage, all the documents are filtered based on the match condition. The input to the projection stage is now a set of documents that have been filtered as part of the match stage.
So a positional operator in the projection stage makes no sense, since in the current stage it doesn't know on what basis the fields had been filtered. Therefore, $ operators are not allowed as part of the field paths.
Why does the below work?
db.class.aggregate([
{ $match: { "students.name": "An" },
{ $unwind: "$students" },
{ $project: { "students": 1 } }
])
As you see, the projection stage gets a set of documents as input, and projects the required fields. It is independent of its previous and next stages.
Try using the unwind operator in the pipeline: http://docs.mongodb.org/manual/reference/operator/aggregation/unwind/#pipe._S_unwind
Your aggregation would look like
db.class.aggregate([
{ $match: { "students.name": "An" },
{ $unwind: "$students" },
{ $project: { "students": 1 } }
])
You can use $filter to selects a subset of an array to return based on the specified condition.
db.class.aggregate([
{
$match:{
"className": "AAA"
}
},
{
$project: {
$filter: {
input: "$students",
as: "stu",
cond: { $eq: [ "$$stu.name", "An" ] }
}
}
])
The following example filters the Students array to only include documents that have a name equal to "An".