Translating "value in array column" MongoDB query to MongoDB Spring Data code - java

How can I translate the following MongoDB query into Java-based query using the Java MongoDB Spring Data driver?
db.User.aggregate([
{ $match : { $expr: { $in: [ "ADMIN", "$roles" ] } } },
{ $sort : { "createdAt": 1 } },
{ $limit : 1 }
])
My attempted solution in Kotlin, which I think is wrong (I don't know how to specify that "roles" is a field in the User document).
fun queryFirstAdmin(): User? {
val matchRolesOpt = Aggregation.match(Criteria.where("ADMIN").`in`("roles"))
val sortOpt = Aggregation.sort(Sort.Direction.ASC, "createdAt")
val limitOpt = Aggregation.limit(1)
var ops: MutableList<AggregationOperation> = mutableListOf()
ops.add(matchRolesOpt)
ops.add(sortOpt)
ops.add(limitOpt)
var aggregation = Aggregation.newAggregation(*ops.toTypedArray())
val aggregationResult = mongoTemplate.aggregate(aggregation, User::class.java, User::class.java)
return aggregationResult.uniqueMappedResult
}
This solution works but unfortunately it returns a slice of the User document (since it has been unwound):
fun queryFirstAdmin(): User? {
val unwindOpt = Aggregation.unwind("roles")
val matchRolesOpt = Aggregation.match(Criteria.where("roles").`is`("ADMIN"))
val sortOpt = Aggregation.sort(Sort.Direction.ASC, "createdAt")
val limitOpt = Aggregation.limit(1)
var ops: MutableList<AggregationOperation> = mutableListOf(unwindOpt, matchRolesOpt, sortOpt, limitOpt)
var aggregation = Aggregation.newAggregation(*ops.toTypedArray())
val aggregationResult = mongoTemplate.aggregate(aggregation, User::class.java, User::class.java)
return aggregationResult.uniqueMappedResult
}
i.e. if the Document returned has the array ["ADMIN"] but the original document has ["ADMIN","USER] in the "roles" field
How can I fix this?

The MongoDB Spring Data Java code for the aggregation:
MongoOperations mongoOps = new MongoTemplate(MongoClients.create(), "test");
Aggregation agg = newAggregation(
match(where("roles").is("ADMIN")),
sort(ASC, "createdAt"),
limit(1L)
);
AggregationResults<Document> results = mongoOps.aggregate(agg, "User", Document.class);
results.forEach(System.out::println);
NOTE:
The match stage in the aggregation
{ $match : { $expr: { $in: [ "ADMIN", "$roles" ] } } }
can be replaced with this and the result is the same:
{ $match : { roles: "ADMIN" } }
The same query using MongoDB Java Driver:
Bson match = match(expr( Document.parse(" { $in: [ 'ADMIN', '$roles' ] } ")));
// -or-
// Bson match = match(eq("roles", "ADMIN"));
List<Bson> pipeline =
Arrays.asList(match, sort(ascending("createdAt")), limit(1));
List<Document> results = new ArrayList<>();
collection.aggregate(pipeline).into(results);
results.forEach(System.out::println);

Related

Convert mongoDB query into Spring Data MongoDB java code

I have the following MongoDB query and I dont know how to convert in Spring Data java code, the group and replaceRoot operations.
db.getCollection('operationData').aggregate([
{ $match: type: "OPERATION_CHEAP", amount: {$gte: 1000},
createdAt: {$gte: ISODate("2020-01-24T23:00:00.000Z")}
},
{ $project: { amount: 1, operationId: 1 } },
{ $sort: { amount: -1 } },
{ $group: { _id: '$operationId', g: { $first: {data: '$$ROOT'} }} }, <----
{ $replaceRoot: { newRoot: '$g.data' }}, <------
{ $sort: { amount: 1 } }
])
This is the code for the match operation:
Criteria criterias = new Criteria()
.andOperator(Criteria.where(Operation.AMOUNT)
.gte(minAmount)
.and(Operation.TYPE).is(OperationTypeEnum.CHEAP_OPERATION)
.and("createdAt").gte(startDate).lte(endDate));
MatchOperation matchOperation = Aggregation.match(criterias);
This is the code for the project operation:
ProjectionOperation projectionOperation = Aggregation.project("amount", "operationId");
And this is the Aggregation operation:
Aggregation aggregation = Aggregation.newAggregation(matchOperation, projectionOperation,
sort(direction, "amount"));
AggregationResults<OperationDataVO> aggregate = mongoTemplate.aggregate(aggregation,
COLLECTION, OperationDataVO.class);
I cant find out how to create and integrate the GroupOperation
Try this way:
Aggregation.group("operationId").first("$$ROOT").as("g");
Aggregation.replaceRoot("g");

Group aggregation using spring data mongodb

I tried to write a group aggregation query using the year value from a date object as a key, but for some reason I'm getting this exception.
org.springframework.data.mapping.PropertyReferenceException: No property year(invoiceDate)
Here is the mongo query which I'm trying to replicate:
db.collection.aggregate([
{
$match:
{
"status": "Active"
}
},
{
$group:
{
"_id":{$year:"$invoiceDate"}
}
},
{
$sort:
{
"_id" : -1
}
}
])
And this is my Java implementation:
Aggregation aggregation = Aggregation.newAggregation(
match(new Criteria().andOperator(criteria())),
Aggregation.group("year(invoiceDate)")
).withOptions(newAggregationOptions().allowDiskUse(true).build());
I also didn't find a way how I can apply the sorting on the results from the grouping.
You're basically looking for extractYear() which maps to the $year operator with MongoDB:
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(new Criteria().andOperator(criteria())),
Aggregation.project().and("invoiceDate").extractYear().as("_id"),
Aggregation.group("_id"),
Aggregation.sort(Sort.Direction.DESC, "_id)
)
This generally needs to go into a $project in order to make the helpers happy.
If you really want the expression within the $group then you can add a custom operation expression:
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(new Criteria().andOperator(criteria())),
new AggregationOperation() {
#Override
public Document toDocument(AggregationOperationContext aggregationOperationContext) {
return new Document("$group",
new Document("_id", new Document("$year","$invoiceDate") )
);
}
},
Aggregation.sort(Sort.Direction.DESC, "_id)
)

Creating Spring Data Aggregation of multiple MongoDB queries

The database MongoDB I have stored documents in the format:
{
"achievement": [
{
"userFromId":"max",
"userToId":"peter",
"date":"2016-01-25",
"pointCount":1,
"description":"good work",
"type":"THANKS"
}
]
}
How to get the number of records in the database (if any) for the a certain date, in which people are thanking the other people.
I created a query to retrieve data:
DBObject clause1 = new BasicDBObject("userFromId", userFromId);
DBObject clause2 = new BasicDBObject("userToId", userToId);
DBObject clause3 = new BasicDBObject("sendDate", localDate);
DBObject clause4 = new BasicDBObject("type", Thanks);
BasicDBList or = new BasicDBList();
or.add(clause1);
or.add(clause2);
or.add(clause3);
or.add(clause4);
DBObject query = new BasicDBObject("$or", or);
But I do not know how to get the number of records and how can rewrite the query using aggregation?
For example:
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.group("userFromId")
.first("userFromId").as("userFromId")
.sum("pointCount").as("pointCount"));
I do not know how to add a few more parameters.
What the return request if the data to the database does not exist?
Thanks for any help
You can use something like this. This will count all the number of documents matching the below criteria.
Regular Query
db.collection.count({ $or: [ { "userFromId": userFromId }, { "userToId": userToId } ] });
Using Aggregation
db.collection.aggregate( [
{ $match: { $or: [ { "userFromId": userFromId }, { "userToId": userToId } ] } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );

Failing to add a second '$or' expression

I am trying to query a mongodb collection with two 'or' clauses via Java API. The following query works fine when run through any mongo client:
{
$and : [{
$or : [ { “field1” : “value1”}, {“field1” : “value2”}]
},{
$or : [ { “field2” : “value3”} , { “field2” : “value4”}]
}]
}
But when I try to run this using Java API, I get the following error:
org.springframework.data.mongodb.InvalidMongoDbApiUsageException: Due to limitations of the com.mongodb.BasicDBObject, you can't add a second '$or' expression specified as '$or : [ { “field1” : “value1”}, {“field1” : “value2”}]'. Criteria already contains '$or : [ { “field2” : “value3”} , { “field2” : “value4”}]'.
at org.springframework.data.mongodb.core.query.Criteria.setValue(Criteria.java:640)
at org.springframework.data.mongodb.core.query.Criteria.getCriteriaObject(Criteria.java:573)
at org.springframework.data.mongodb.core.query.Criteria.createCriteriaList(Criteria.java:630)
at org.springframework.data.mongodb.core.query.Criteria.andOperator(Criteria.java:539)
You can do it through Java API by
1) preparing a list of your 'or' criterias.
2) create new criteria object.
3) 'feed' the list created in (1) into the object created in (2),
using the criteria object's 'andOperator' method.
Something like this ->
Query query = new Query();
Map<String, String> params; // let's say this arrived with some values
List<Criteria> criterias = new ArrayList<>();
for (String k : params.keySet()) {
String paramVal = params.get(k);
switch (k) {
case "someVal1":
criterias.add(new Criteria().orOperator(Criteria.where("metadata.field1").is(paramVal),
Criteria.where("metadata.field2").is(paramVal)));
break;
case "someVal2":
criterias.add(new Criteria().orOperator(Criteria.where("metadata.arrayField3").in(paramVal),
Criteria.where("metadata.arrayField4").in(paramVal),
Criteria.where("metadata.arrayField5").in(paramVal)));
break;
default:
break;
}
}
if (criterias.size() > 0){
query.addCriteria(new Criteria().andOperator(criterias.toArray(new Criteria[criterias.size()])));
}
Try splitting that into an $in operator. Let's break down the first $or expression:
{
"$or": [
{ "filed1" : "value1" },
{ "filed1" : "value2" }
]
}
This can be converted into an $in operator as
{ "field1": { "$in": ["value1", "value2"] } }
Similarly
{
"$or": [
{ "filed2" : "value3" },
{ "filed2" : "value4" }
]
}
can be expressed as
{ "field2": { "$in": ["value3", "value4"] } }
Combining the two expressions into one implicit AND query yields:
db.collection.find({
"field1": { "$in": ["value1", "value2"] },
"field2": { "$in": ["value3", "value4"] }
})
If possible, upgrade to 3.x driver and stop using the old and obsolete handcrafted bson documents.
This works under 3.x:
public static void main(String[] args) throws Exception {
try (
MongoClient mongoClient = new MongoClient();
) {
MongoCollection<Document> coll = mongoClient.getDatabase("test").getCollection("mycoll");
Bson f = Filters.and(
Filters.or(
Filters.eq("field_a", "Test 1"),
Filters.eq("field_a", "Test 2")
),
Filters.or(
Filters.eq("field_b", 1999),
Filters.eq("field_b", 2000)
)
);
coll.find(f).forEach((Consumer<Document>) d -> System.out.println(d));
}
}
This worked for me,,
Query query = new Query(new Criteria().orOperator(
Criteria.where("field1").regex(searchStr),
Criteria.where("field2").regex(searchStr),
Criteria.where("field3").regex(searchStr)));
List<XXX> result = mongoTemplate.find(query, XXX.class);

implementing mongodb query in java using java mongo driver

I am using the below mongo query to get the max temperature. Can any one help how to implement in java using mongo java driver using BasicDBObject and DBObject?
db.EventLog.aggregate(
[
{
$group:
{
_id: "$_id",
maxInnerTemp: { $max: { $concat : [ "0", "$fields.innerTemp"]}}
}
}
]
)
Use mongo java aggregation like below code ( not tested ) :
// $group operation
BasicDBList concat = new BasicDBList();
concat.add("0");
concat.add("$fields.innerTemp");
DBObject groupFields = new BasicDBObject("_id", "$_id");
groupFields.put("maxInnerTemp", new BasicDBObject("$max", new BasicDBObject("$concat", concat));
DBObject group = new BasicDBObject("$group", groupFields);
// run aggregation
List < DBObject > pipeline = Arrays.asList(group); AggregationOutput output = collectionName.aggregate(pipeline);
for (DBObject result: output.results()) {
System.out.println(result);
}

Categories

Resources