Aggregation on spring mongo - java

I am trying to recreate this query on spring mongo layer.
db.LASTVIEWED_TEST.aggregate([{$match: {'_id' : '12070'}},
{$unwind:"$value"},
{$match:{"value.Dockey":{"$in":["390", "539","626"]}}},
{$group:{_id:"$_id", "value":{$push:"$value"}}}
])
I have made some attempts using various methods however I came across the examples here:
https://github.com/spring-projects/spring-data-mongodb/blob/master/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java
and created this:
Aggregation agg = Aggregation.newAggregation(//
match(where("entityid").is(entityId)),//
unwind("$value"), //
match(where("value.Dockey").in(dockeyList)),//
group("id").push("$value").as("value")//
);
However this builder does not see to recognise the where, unwind keywords etc... and my compiler tells me my class does not have these methods.
What do I need to do to pass values to the newAggregation builder.
cheers,

You need to specify a Criteria object in the match, and generally all values are just represented as strings:
Aggregation agg = newAggregation(
match(Criteria.where("entityid").is(entityId)),
unwind("value"),
match(Criteria.where("value.Dockey").in(dockeyList)),
group("_id").push("value").as("value")
);

Related

MongoDB java driver passing undefined as value

I've below mongoDB aggregation which is filtering tests array in my mongodb collection item.
Sample collection item : {,...tests:[ {} , {"someField":"yesIamHere"} ] }
Below query worked well and returned only tests collection which contains someField
db.getCollection('yourcollection')
.aggregate([
{"$match": {"tests.someField": {"$exists": true}}},
{ $project:{"tests": {"$filter": {"input": "$tests", "as": "item",
"cond": {"$ne": ["$$item.someField", undefined]}}}}
},
])
However,
While using java BasicDBObject is taking "undefined" as string not JS undefined
BasicDBObject projectionFilterInput=new BasicDBObject("input","$tests")
.append("as", "item")
.append("cond",new BasicDBObject("$ne", Arrays.asList("$$item.someField","undefined")));
So, this interprets "cond": {"$ne": ["$$item.vidaptorCode", "undefined"]}}}} "undefined" not undefined. So, this doesn't filter the items as intended.
Is there any constant defined for this specific undefined value in mongodb java driver base ? This is the main question.
Whoever curious...
Why am I not using ODM ?
Actually we do use Spring Data for MongoDB, but it doesn't support this aggregation cond.
MatchOperation matchStage = Aggregation.match(new Criteria("tests.someField").exists(true));
ProjectionOperation projection = Aggregation.project("tests");
Aggregation aggregation
= Aggregation.newAggregation(matchStage, projection);
AggregationResults<LabConfiguration> output
= mongoTemplate.aggregate(aggregation, "yourcollection", YourClass.class);
Morphia ODM
I liked Morphia fluent syntax however they're using different annotations than Spring Data Mongo and its dependent MongoDB libs are different. In short, two ODM don't work together.
The biggest problem is for repository implementation you need to implement BasicDAO<C,K> and it's not very practical, it's mongo oriented and Spring Data Mongo does great job with MongoRepository<C,K>
Projections filterProjection = projection(
"tests",
expression(
"$filter",
new BasicDBObject("input","$tests")
.append("as", "item")
.append("cond",new BasicDBObject("$ne", Arrays.asList("$$item.someField","undefined")))
)
);
Hence, I ended up with Mongo driver base syntax for this problem that's why I need to pass undefined to BasiDBObject but not as a string covered by double quotes.
I'm also open to hear your overall advices. What we have now is QueryDSL and Spring Data for MongoDB.
As commented by others in my OP, the alternative is using $ifNull.
Actually, I was expecting using an {extists:true} but it's not a valid aggregation cond operator or something like $ifNotNull would be nice, however it's also achievable with $ifNull.
As you know projection works with 0 and 1, i.e. {name:1,_id:0}
So, I decided to return 0 when $ifNull and it worked!
Java MongoDB Core Driver way
BasicDBObject projectionFilterInput=new BasicDBObject("input","$tests")
.append("as", "item")
.append("cond",new BasicDBObject("$ifNull",Arrays.asList("$$item.someField",0)));

How to get the newest record for every user using spring data mongodb?

I am struggling with a mongo query. I need to find a collection of documents in single query. The collection should contain document with newest date (field createdAt) for every user in single query.
There is a test case in Spock to demonstrate what I am trying to acheive:
def 'should filter the newest location for every user'() {
given:
List locationsInDb = [
buildLocation(USERNAME_1, '2017-02-03T10:37:30.00Z'),
buildLocation(USERNAME_1, '2017-03-04T10:37:30.00Z'),
buildLocation(USERNAME_2, '2017-02-05T10:37:30.00Z'),
buildLocation(USERNAME_2, '2017-03-06T10:37:30.00Z')
]
insertToMongo(locationsInDb)
when:
List filteredLocations = locationRepository.findLastForEveryUser()
then:
filteredLocations == [locationsInDb.get(1), locationsInDb.get(3)]
}
I found that distinct methods are a part of 2.1.0.M1 version so they are not available yet.
I was also trying with #Query annotation but the documentation (link below) does not specify how to create a query like mine.
https://docs.spring.io/spring-data/data-document/docs/current/reference/html/#d0e3309
Thanks for your help.
There are no means to express the query you are looking for via a derived query in Spring Data, nor using the MongoDB native query operators. Distinct as well will not do the job as it just extracts distinct values of a single field into an array.
Please consider using an Aggregation. Spring Data specifics can be found in the reference documentation.

Spring Data MongoDB: search like on multiple fields

I have a MongoDB collection containing User objects with two fields: Firstname and Lastname. I need a query that takes only one string (representing the user fullname) for a findLike research.
The problem is the same of this question but I do not know how translate that query for a MongoDB Repository in Spring Data using MongoTemplate or #Query annotation
EDIT:
Using project operator i have to specify all fields I want include in the stages. A better solution maybe could be use AddFields operator:
A similar question I found is that:
https://stackoverflow.com/a/40812293/6545142
How can I use the $AddFields operator with MongoTemplate?
You can use $expr ( 3.6 mongo version operator ) to use aggregation functions in regular query for only exact matches.
Spring #Query code
#Query("{$expr:{$eq:[{$concat:["$Firstname","$Lastname"]}, ?0]}}")
ReturnType MethodName(ArgType arg);
For find like searches or exact search you've to use aggregation via mongo template in lower versions.
AggregationOperation project = Aggregation.project().and(StringOperators.Concat.valueOf("Firstname").concatValueOf("Lastname")).as("newField");
for like matches
AggregationOperation match = Aggregation.match(Criteria.where("newField").regex(val));
for exact match
AggregationOperation match = Aggregation.match(Criteria.where("newField").is(val));
Rest of the code
Aggregation aggregation = Aggregation.newAggregation(project, match);
List<BasicDBObject> basicDBObject = mongoTemplate.aggregate(aggregation, colname, BasicDBObject.class).getMappedResults();

Spring Data JPA with QueryDSL, Count issue with aggregate function

I am using Spring Data JPA with QueryDSL and trying to use Sum function in where condition, As i am using pagination so i have to get count first.
So i have java code like below :-
NumberPath<Double> path = entityPath.getNumber("qty", Double.class);
BooleanExpression exp = path.sum().loe(120);
JPQLQuery countQuery = from(stock).where(exp);
long count = countQuery.count();
its creating query like this :-
select count(stock0_.stock_id) as col_0_0_ from stock stock0_
where sum(stock0_.qty)>=120;
and i am getting Error Code: 1111. Invalid use of group function.
above query is not working in SQL as well because sum function cant be use with count in where condition. I have not idea how to deal with such problem when i have to get count first and then fetch the real data.
Can someone please help me out with what is JPA approach to deal with such issue.
Please don't suggested #Query annotation because i can not use it. Due to dynamic filtration requirement.
You are using aggregate functions (sum). Then you need having() instead where()
This example is a really large one. You only need to care about pagination and having elements.
If you got different predicates and a pageRequest object.
Page request example:
pageRequest = new PageRequest(
MI_PAGE,
LIMIT,
Sort.Direction.valueOf( ASC ),
ORDER_FIELD);
EntityQ represents a QueryDSL generated meta-entity.
Query example:
new JPAQuery(em).from(entityQ).where( entityQ.id.in(
new JPASubQuery()
.from(entity2Q).innerJoin(entity2Q.objEntityQ, entityQ)
.where(ENTITY, PREDICATE)
.groupBy(entity2Q.objEntityQ.id)
.having( Wildcard.count.goe(SIZE) )
.list(entity2Q.objEntityQ.id)
))
.offset(pageRequest.getOffset() )
.limit( pageRequest.getPageSize() )
.orderBy(ORDERS).list(entityQ);
EDIT 2 More specific for your example:
I usually have a persistence context in my custom repositories:
#PersistenceContext
EntityManager em
Then, you could create a simple JPAQuery:
new JPAQuery(em)
.from(YOUR_STOCK_QUERYDSL_ENTITY)
.groupBy(YOUR_STOCK_QUERYDSL_ENTITY.field_to_sum)
.having(YOUR_STOCK_QUERYDSL_ENTITY.field_to_sum.sum().as("alias")
.goe(100));

Filter custom fields not present in database using Apache Cayenne

In my API it is currently possible to filter on all fields that exists in the database.
Filtering is implemented in FilterUtils.java in the API project. This translates the url parameters to a Apache Cayenne search and returns the result to the resource.
In the "Main" project there are generated classes for each database table in com.foo.bar.auto these are extended by classes in com.foo.bar.
The classes in com.foo.bar can have custom functions. An example is Document.getAccount.
Document.getAccount is exposed in the API, but it is not able to filter it because it's not a database field. I need to be able to filter fields like Document.getAccount.
Is it possible to register these functions in Cayenne somehow?
The syntax for searching on custom fields needs to be equal to todays filtering syntax. So when searching for account it should look like this: Document?filter=account(EQ)1234.
Any ideas? All help is appreciated.
Your best bet is to split your filter keys into persistent and non-persistent properties. Then you'd build 2 expressions, one for each subset of keys. Use the first expression to build a query to fetch from DB, and the second one - to filter the returned result in memory:
Expression p = ...
Expression np = ...
SelectQuery query = new SelectQuery(Document.class, p);
List<Document> docs = context.performQuery(query);
List<Document> filteredDocs = np.filterObjects(p);

Categories