Spring Data - MongoDb aggregation $ifNull - java

db.collection.aggregate([
{$match : { name : "name" } },
{$project: {
name: 1,
sent: {
$size: {
"$ifNull": [ "$audience", [] ]
}
}
}
}]);
How can I do the above mongo aggregation with Spring data?

I know this is an old post and you might have found the answer, but, just for the sake of others, I'm posting it here.
Aggregation aggregation = Aggregation.newAggregation(
.match(Criteria.where("name").is("name"))
.project("name")
.and(ArrayOperators.Size.lengthOfArray(ConditionalOperators.ifNull("audience").then(Collections.emptyList()))).as("sent")
);

Related

how to give $toObjectId in Java Mongo Projection Operation

I am new bee to mongo. Below is the Aggregation operation I am doing in mongodb shell . But In my java ProjectionAggregation , am not able to give the $toObjectId. Please correct me what am I missing.
db shell query
db.getCollection('UserData').aggregate([
{
$project : {
"username" : "$username",
"beneficiaries" : "$beneficiaries"
}
},
{
$unwind : {
path : "$beneficiaries",
preserveNullAndEmptyArrays: true
}
},
{
$project : {
"username" : "$username",
"beneficiaries" : "$beneficiaries",
---- dont know how to give $toObjectId in java ProjectionOperation .
"beneficiaryStudId" : { $toObjectId : "$beneficiaries.studentId" }
}
},
{
$lookup:
{
from: "StudentProfileData",
localField: "beneficiaryStudId",
foreignField: "_id",
as: "studProfile"
}
}
])
Java code Projection Operation
ProjectionOperation projectUserAndBeneficiaries = Aggregation.project()
.andExpression("username").as("username")
.andExpression("beneficiaries").as("beneficiaries");
ProjectionOperation projectUserAndOtherDetails = Aggregation.project()
.andExpression("username").as("username")
.andExpression("beneficiaries").as("beneficiaries")
---- How to give $toObjectId in projection operation .andExpression("beneficiaries.studentId").as("beneficiaryStudId");
LookupOperation lookupOperation = LookupOperation.newLookup().
from("StudentProfileData").
localField("beneficiaryStudId").
foreignField("_id").
as("studProfile");
Aggregation agg = Aggregation.newAggregation(projectUserAndBeneficiaries, unwindBeneficiars,
projectUserAndOtherDetails
,lookupOperation);
AggregationResults<UserAndStudentData> output
= mongotemplate.aggregate(agg, "UserData", UserAndStudentData.class);
Sample Ouput
Output in db shell
{
"_id" : ObjectId("5d2f08574de2690001c281ac"),
"username" : "ks241#goo.com",
"beneficiaries" : {
"studentId" : "5d2f0e9c3bcf3e0001a7e562",
"mcBeneficiaryId" : "597418",
"enabled" : true
},
"beneficiaryStudId" : ObjectId("5d2f0e9c3bcf3e0001a7e562"),
"studProfile" : [
{
"_id" : ObjectId("5d2f0e9c3bcf3e0001a7e562"),
"lastName" : "Sharma",
"firstName" : "Kapil",
"studentRegisterCustomFieldValues" : [
{
"bcfdValue" : "One",
"bcfdName" : "Year"
}
],
"gender" : "M",
"merchantId" : "38788943"
}
]
}
where as in java
the studProfile array is always empty if I add $toObjectId the above java aggregation projection query and ran it produces the values as same as that db shell .
Spring data doesn't support to few type of methods. This problem may include into it. But we may use this solution.
Aggregation aggregation=newAggregation(
p-> new Document("$project",
new Document()
.append("username","$username"),
.append("beneficiaries","$beneficiaries)
.append("beneficiaryStudId",
new Document("$toObjectId","$beneficiaries.studentId")
)
)
)
There is a way to do this with Spring's ProjectionOperation and ConvertOperators classes:
ProjectionOperation projection = Aggregation.project()
.andInclude("username", "beneficiaries")
.and(ConvertOperators.ToObjectId.toObjectId("$beneficiaries.studentId"))
.as("beneficiaryStudId");

Springboot: How to execute custom mongodb query by springboot?

I want to find a list of models matched either field nameEnglish or nameChinese by a keyword. I spent more than an hour googling but I cannot do it. Please help.
Springboot Mongo starter example https://spring.io/guides/gs/accessing-data-mongodb/
The custom query I want to execute and return a list result
db.mymodel.aggregate([
{
$match: {
$or :[
{ nameChinese: /門/ },
{ nameEnglish: /cocina/i }
]
}
},
{ $sort: {nameEnglish: 1} }
])
My best trial so far
interface MyModelRepository : MongoRepository<MyModel, String> {
#Query(value = "{ '\$match': { \$or: [ {'nameEnglish': { \$regex: ?0 } }, {'nameChinese': { \$regex: ?0 } } ] }")
fun findByMyQuery(name: String): List<MyModel>
}
For the regex, I also want it to be case insensitive.
I found the answer after a hot water shower ( and one day of googling and reading documents). Hope this help someone in the future.
#Query(value = "{ \$or: [ {'nameEnglish': { \$regex: ?0, \$options: 'i'}}, {'nameChinese': { \$regex: ?0 }}] }", sort = "{nameEnglish: 1}")

Two Aggregate Totals in One Group

I wrote a query in MongoDB as follows:
db.getCollection('student').aggregate(
[
{
$match: { "student_age" : { "$ne" : 15 } }
},
{
$group:
{
_id: "$student_name",
count: {$sum: 1},
sum1: {$sum: "$student_age"}
}
}
])
In others words, I want to fetch the count of students that aren't 15 years old and the summary of their age. The query works fine and I get two data items.
In my application, I want to do the query by Spring Data.
I wrote the following code:
Criteria where = Criteria.where("AGE").ne(15);
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(where),
Aggregation.group().sum("student_age").as("totalAge"),
count().as("countOfStudentNot15YearsOld"));
When this code is run, the output query will be:
"aggregate" : "MyDocument", "pipeline" :
[ { "$match" { "AGE" : { "$ne" : 15 } } },
{ "$group" : { "_id" : null, "totalAge" : { "$sum" : "$student_age" } } },
{ "$count" : "countOfStudentNot15YearsOld" }],
"cursor" : { "batchSize" : 2147483647 }
Unfortunately, the result is only countOfStudentNot15YearsOld item.
I want to fetch the result like my native query.
If your're asking to return the grouping for both "15" and "not 15" as a result then you're looking for the $cond operator which will allow a "branching" based on conditional evaluation.
From the "shell" content you would use it like this:
db.getCollection('student').aggregate([
{ "$group": {
"_id": null,
"countFiteen": {
"$sum": {
"$cond": [{ "$eq": [ "$student_age", 15 ] }, 1, 0 ]
}
},
"countNotFifteen": {
"$sum": {
"$cond": [{ "$ne": [ "$student_age", 15 ] }, 1, 0 ]
}
},
"sumNotFifteen": {
"$sum": {
"$cond": [{ "$ne": [ "$student_age", 15 ] }, "$student_age", 0 ]
}
}
}}
])
So you use the $cond to perform a logical test, in this case whether the "student_age" in the current document being considered is 15 or not, then you can return a numerical value in response which is 1 here for "counting" or the actual field value when that is what you want to send to the accumulator instead. In short it's a "ternary" operator or if/then/else condition ( which in fact can be shown in the more expressive form with keys ) you can use to test a condition and decide what to return.
For the spring mongodb implementation you use ConditionalOperators.Cond to construct the same BSON expressions:
import org.springframework.data.mongodb.core.aggregation.*;
ConditionalOperators.Cond isFifteen = ConditionalOperators.when(new Criteria("student_age").is(15))
.then(1).otherwise(0);
ConditionalOperators.Cond notFifteen = ConditionalOperators.when(new Criteria("student_age").ne(15))
.then(1).otherwise(0);
ConditionalOperators.Cond sumNotFifteen = ConditionalOperators.when(new Criteria("student_age").ne(15))
.thenValueOf("student_age").otherwise(0);
GroupOperation groupStage = Aggregation.group()
.sum(isFifteen).as("countFifteen")
.sum(notFifteen).as("countNotFifteen")
.sum(sumNotFifteen).as("sumNotFifteen");
Aggregation aggregation = Aggregation.newAggregation(groupStage);
So basically you just extend off of that logic, using .then() for a "constant" value such as 1 for the "counts", and .thenValueOf() where you actually need the "value" of a field from the document, so basically equal to the "$student_age" as shown for the common shell notation.
Since ConditionalOperators.Cond shares the AggregationExpression interface, this can be used with .sum() in the form that accepts an AggregationExpression as opposed to a string. This is an improvement on past releases of spring mongo which would require you to perform a $project stage so there were actual document properties for the evaluated expression prior to performing a $group.
If all you want is to replicate the original query for spring mongodb, then your mistake was using the $count aggregation stage rather than appending to the group():
Criteria where = Criteria.where("AGE").ne(15);
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(where),
Aggregation.group()
.sum("student_age").as("totalAge")
.count().as("countOfStudentNot15YearsOld")
);

Mongodb Java driver use limit in aggregation query

Problem
Query is working fine but not limit and skip, it is fetching all records at once.
Kindly suggest what I am doing wrong.
MongoDB Collection
{
"_id" : ObjectId("559666c4e4b07a176940c94f"),
"postId" : "559542b1e4b0108c9b6f390e",
"user" : {
"userId" : "5596598ce4b07a176940c943",
"displayName" : "User1",
"username" : "user1",
"image" : ""
},
"postFor" : {
"type": "none",
"typeId" : ""
},
"actionType" : "like",
"isActive" : 1,
"createdDate" : ISODate("2015-07-03T10:41:07.575Z"),
"updatedDate" : ISODate("2015-07-03T10:41:07.575Z")
}
Java driver Query
Aggregation aggregation = newAggregation(
match(Criteria.where("isActive").is(1).and("user.userId").in(feedUsers)),
group("postId")
.last("postId").as("postId")
.last("postFor").as("postFor")
.last("actionType").as("actionType")
.last("isActive").as("isActive")
.last("user").as("user")
.last("createdDate").as("createdDate")
.last("updatedDate").as("updatedDate"),
sort(Sort.Direction.DESC, "createdDate")
);
aggregation.skip( skip );
aggregation.limit( limit );
AggregationResults<UserFeedAggregation> groupResults =
mongoOps.aggregate(aggregation, SocialActionsTrail.class, UserFeedAggregation.class);
return groupResults.getMappedResults();
Thanks
Aggregation pipelines are "sequential" in operation. These are not like .find() operations where .sort() .limit() .skip() are "modifiers" to the query operation:
Aggregation aggregation = newAggregation(
match(Criteria.where("isActive")
.is(1).and("user.userId").in(feedUsers)),
group("postId")
.last("postId").as("postId")
.last("postFor").as("postFor")
.last("actionType").as("actionType")
.last("isActive").as("isActive")
.last("user").as("user")
.last("createdDate").as("createdDate")
.last("updatedDate").as("updatedDate"),
sort(Sort.Direction.DESC, "createdDate"),
skip( skip ),
limit( limit )
);
Unless you define the operations in "sequence" then the pipeline does not know the order of execution. So define the pipeline as a whole.
A basic example:
Aggregation aggregation = newAggregation(
group("postId"),
skip(1),
limit(1)
);
System.out.println(aggregation)
Outputs a perfect pipeline:
{
"aggregate" : "__collection__" ,
"pipeline" : [
{ "$group" : { "_id" : "$postId" } },
{ "$skip" : 1 },
{ "$limit" : 1 }
]
}
See $skip and $limit in the core documentation.

Create aggregation in mongodb with spring

I have aggregation that works in mongo and i need to create the exact one in java with spring. I didn't find a way. Do you know if there is one?
db.collection_name.aggregate([
{
$group: {
_id : {
year : {$year : "$receivedDate" },
month : {$month: "$receivedDate"},
day : { $dayOfMonth : "$receivedDate"}
},
count : { $sum: 1 }
}
}
])
You could try projecting the fields first by using the SpEL andExpression in the projection operation and then group by the new fields in the group operation:
Aggregation agg = newAggregation(
project()
.andExpression("year(receivedDate)").as("year")
.andExpression("month(receivedDate)").as("month")
.andExpression("dayOfMonth(receivedDate)").as("day"),
group(fields().and("year").and("month").and("day"))
.count().as("count")
);

Categories