Implement MongoDB aggregation pipeline with $group stage in java spring - spring

I am trying to implement a MongoDB query in java using Spring mongo.
This is the native MongoDB query:
db.ShieldReport.aggregate([
{$match:{"sellerCode":"e1aaf3"}},
{$project:{bucketName:"$bucketName", brandName: "$brandName", createdTime : "$createdTime", sellerCode : "$sellerCode"}},
{$sort:{"createdTime":-1}},
{$group:
{
_id: { sellerCode: "$sellerCode", bucketName: "$bucketName", brandName: "$brandName"},
itemsSold: { $first: { bucketName: "$bucketName", brandName: "$brandName", createdTime : "$createdTime"} }
}
},
{$sort:{"itemsSold.createdTime":-1}},
{$project : { _id : "$_id.sellerCode", bucketName :"$itemsSold.bucketName", brandName : "$itemsSold.brandName"}}
])
In my Spring java version, I have got it this far:
Aggregation agg = newAggregation(
match(Criteria.where("sellerCode").is(filterR‌​equest.getSellerCode‌​())),
Aggregation.project("bucketName")
.andInclude("brandName")
.an‌​dInclude("createdTim‌​e")
.andInclude("sell‌​erCode"),
sort(Sort.Direction.DESC, "createdTime"),
group("sellerCode", "brandName", "bucketName")
);
But the problem is I am not able to create the itemSold field. How do I create that?

You could restructure your pipeline in Spring as follows:
Aggregation agg = Aggregation.newAggregation(
Aggregation.match(Criteria.where("sellerCode").is(filterR‌​equest.getSellerCode‌​())),
Aggregation.sort(Sort.Direction.DESC, "createdTime"),
Aggregation.group("sellerCode", "bucketName", "brandName")
.first("bucketName").as("bucketName")
.first("brandName").as("brandName")
.first("createdTime ").as("createdTime "),
Aggregation.sort(Sort.Direction.DESC, "createdTime"),
Aggregation.project("bucketName", "brandName")
.and("sellerCode").previousOperation()
);
As you can see some of the pipeline stages have been omitted because they are really not necessary. For instance, the $project pipeline before the $group step can be taken off since the $group pipeline operator will apply the accumulators
In the group step, you group the intermediate filtered documents by the three fields and store the other field values in new fields aided by the $first operator.
Sort the intermediate result by the createdTime field reference of the previous group operation.
Finally in the fourth step, select the "bucketName" and "brandName" fields from the previous group operation. Note that "sellerCode" again implicitly references an group-id field.

Related

Spring-Mongo-Data Update only allows one positional argument?

I have a data model with the following structure:
{
_id: ObjectId(''),
strs: [
{
_id: ObjectId(''),
nds: [
{
_id: ObjectId(''),
title: ''
},
.
.
.
]
},
.
.
.
]
}
Following query works perfectly fine in mongo shell:
mongo.update({_id: ObjectId(''), 'strs._id': ObjectId(''), 'strs.nds._id': ObjectId('')}, {$set: {'strs.$.nds.$.title': 'new-title'}})
I am trying to do the same in Spring Boot, I have written the following lines of code:
Criteria criteria = new Criteria();
criteria.addOperator(Criteria.where("id").is(id),
Criteria.where("strs.id").is(strsId), Criteria.where("strs.nds.id", ndsId));
Query query = new Query().addCriteria(criteria);
Update update = new Update();
update.set("strs.$.nds.$.title", title);
mongoTemplate.findAndModify(query, update, MyModel.class);
But, this is not working as expected. It's saying that mongo cannot create a title field inside [nds: {...}]. So, I logged the queries MongoTemplate was generating, and it turns out that MongoTemplate was removing the second positional argument $ from the query.
This was the generated query:
mongo.update({...}, {$set: {'strs.$.nds.title': 'new-title'}})
And this was the reason mongo was throwing an exception saying it cannot create a title field in an array.
Am I doing this wrong? Because MongoTemplate is generating an invalid query which is failing (obviously).
What you can do is
update.set("strs.$[elmStr].nds.$[elmNds].title", title)
.filterArray("elmStr._id", strsId)
.filterArray("elmNds._id",ndsId);
And refer positional operator

Filter by Date with Spring Data MongoTemplate

I would like to filter my filedA's array by dates, with a mongo query it looks like that :
{
$project: {
user: "$$ROOT",
fieldA: {
$filter: {
input: "$fieldA",
as: "a",
cond: {
$and: [
{$lt: ["$$a.constraint", new Date()]},
{$gt: ["$$a.constraint", new Date()]}
]
}
}
}
}
},
The query works but I have trouble when I tried to do it with spring :
project()
.and("$$ROOT").as("user")
.and(
filter("$fieldA")
.as("a")
.by(
and(
ComparisonOperators.Lte.valueOf("a.constraint")
.lessThanEqualTo(dateEnd),
ComparisonOperators.Gte.valueOf("a.constraint")
.greaterThanEqualTo(dateStart)
)
)).as("fieldA"),
I think this is not the right way to make dates comparations but I don't know how to do it properly. Could help me figure out what I'm doing wrong?
try wrapping your dateStart and dateEnd variables in ConvertOperators.ToDate.toDate.
The ComparisonOperators methods (in this case lessThanEqualTo and greaterThanEqualTo) accept either String or AggregationExpression.
By using the ConvertOperators.ToDate.toDate method, you ensure that the passed argument is AggregationExpression and the value is correctly formatted for comparing.
Result would look like this:
import static org.springframework.data.mongodb.core.aggregation.ConvertOperators.ToDate.toDate
...
project()
.and("$$ROOT").as("user")
.and(
filter("$fieldA")
.as("a")
.by(
and(
ComparisonOperators.Lte.valueOf("a.constraint")
.lessThanEqualTo(toDate(dateEnd)),
ComparisonOperators.Gte.valueOf("a.constraint")
.greaterThanEqualTo(toDate(dateStart))
)
)).as("fieldA"),

What kind of Java type to pass into Criteria.all()?

I am trying to find a document with an array of tags which match a list of values,
using the MongoDB's $all function through Spring Data MongoDB API for all().
Tag is a embedded document with two fields: type and value.
I am not sure what kind of Java type to pass in to the method as it accepts an array of Objects, tried to pass in an array of Criteria objects into the the function but the output is below:
Query: { "tags" : { "$all" : [ { "$java" : org.springframework.data.mongodb.core.query.Criteria#5542c4fe }, { "$java" : org.springframework.data.mongodb.core.query.Criteria#5542c4fe } ] } }, Fields: { }, Sort: { }
How should I proceed?
I want to achieve the following:
db.template.find( { tags: { $all: [ {type:"tagOne", value: "valueOne"}, {type:"tagTwo", value: "valueTwo"} ] } } )
Edited for clarity:
The code which I used is similar to:
Query query = new Query(baseCriteria.and("tags").all( criteriaList.toArray()))
The criteria list is formed by:
Criteria criteria = new Criteria();
criteria.and("type").is(tag.getType()).and("value").is(tag.getValue());
criteriaList.add(criteria);
OK, I've just found out, the Java type required is org.bson.Document, which you can get using:
criteria.getCriteriaObject()

GraphQL query based on a specific value of a field

I want to be able to retrieve the latest release from GitHub for a specific repo using their GraphQL API. To do that, I need to get the latest release where isDraft and isPrerelease are false. I have managed to get the first part, but cant figure out how to do the "where" part of the query.
Here is the basic query I have gotten (https://developer.github.com/v4/explorer/):
{
repository(owner: "paolosalvatori", name: "ServiceBusExplorer") {
releases(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {
nodes {
name
tagName
resourcePath
isDraft
isPrerelease
}
}
}
}
Which returns:
{
"data": {
"repository": {
"releases": {
"nodes": [
{
"name": "3.0.4",
"tagName": "3.0.4",
"resourcePath": "/paolosalvatori/ServiceBusExplorer/releases/tag/3.0.4",
"isDraft": false,
"isPrerelease": false
}
]
}
}
}
}
I cant seem to find a way to do this. Part of the reason is that I am new to GraphQL (first time trying to do a query) and I am not sure how to frame my question.
Can one only "query" based on those types that support arguments (like repository and releases below)? Seems like there should be a way to specify a filter on the field values.
Repository: https://developer.github.com/v4/object/repository/
Releases: https://developer.github.com/v4/object/releaseconnection/
Node: https://developer.github.com/v4/object/release/
Can one only "query" based on those types that support arguments
Yes: GraphQL doesn't define a generic query language in the same way, say, SQL does. You can't sort or filter a field result in ways that aren't provided by the server and the application schema.
I want to be able to retrieve the latest [non-draft, non-prerelease] release from GitHub for a specific repo using their GraphQl API.
As you've already found, the releases field on the Repository type doesn't have an option to sort or filter on these fields. Instead, you can iterate through the releases one at a time with multiple GraphQL calls. These would individually look like
query NextRelease($owner: String!, $name: String!, $after: String) {
repository(owner: $owner, name: $name) {
releases(first: 1,
orderBy: {field: CREATED_AT, direction: DESC},
after: $after) {
pageInfo { lastCursor }
nodes { ... ReleaseData } # from the question
}
}
}
Run this in the same way you're running it now (I've split out the information identifying the repository into separate GraphQL variables). You can leave off the after variable for the first call. If (as in your example) it returns "isDraft": false, "isPrerelease": false, you're set. If not, you need to try again: take the value from the lastCursor in the response, and run the same query, passing that cursor value as the after variable value.
{
repository(owner: "paolosalvatori", name: "ServiceBusExplorer") {
releases(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {
nodes(isDraft :false , isPrerelease :false ) {
name
tagName
resourcePath
isDraft
isPrerelease
}
}
}
}
Alternatively please have look at GraphQL directives, as sometimes it's required to skip or include the fields on the basis of the values
#skip or #include.
The skip directive, when used on fields or fragments, allows us to exclude fields based on some condition.
The include directive, allows us to include fields based on some condition
GraphQL Directives

How can we write mongo db query in Spring

I have a Feed collection that has two fields: username and sentiment. I have to write a aggregation in spring to group by username and sentiment. I also have to display total number of times username comes in collection.
How to do that?
My code is:
db.feed.aggregate([
{$group: {_id : {username : '$username',sentiment:'$sentiment'}, total:{$sum :1}}},
{$project : {username : '$_id.username', sentiment : '$_id.sentiment', total : '$total', _id : 0}}
])
The first thing that comes to mind:
http://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo.aggregation
So in your case it would look something like:
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
class Stat {
String username;
String sentiment;
int total;
}
Aggregation agg = newAggregation(
group("username", "sentiment").count().as("total"),
project().
and("_id.username").as("username").
and("_id.sentiment").as("sentiment").
and("total").as("total")
);
AggregationResults<Stat> results = mongoTemplate.aggregate(agg, "feed", Stat.class);
List<Stat> mappedResult = results.getMappedResults();

Resources