How to dynamically construct MongoDB Criteria object for Aggregation.match? - java

I have the following JSON object which I would like to convert to a Criteria object so that I can use in Aggregation.match() query.
{
"_filter": {
"$and": [
{
"$or": [
{
"country": "India"
},
{
"age": 20
}
]
}
]
},
"_page": {
"pageNum": 0,
"recordsPerPage": 1
}
}
I have seen that we have BasicQuery object which can be constructed from the contents of the _filter field. However, I am not able to deduce Criteria object from that.
Is there any method/utility in Spring Data MongoDB that does this?

Actually Criteria is a wrapper class to abstract you from the MongoDB syntax and build the queries in an elegant way.
I would like to convert to a Criteria object so that I can use in Aggregation.match() query
There is no need to do so. Try this:
AggregationOperation match = ctx => new Document("$match", Document.parse(your_json).get("_filter"));
...
Aggregation agg = Aggregation.newAggregation(match)
.withOptions(Aggregation.newAggregationOptions().allowDiskUse(Boolean.TRUE).build());
mongotemplate.aggregate();
mongoTemplate.aggregate(agg, inputType, outputType).getMappedResults();

Related

Finding exact match in MongoDB query where search criteria is applied to the object level in the list

I have a requirement of fetching data from mongodb and being done using Java Spring Reactive Mongodb library. I am using the following code for this:
Criteria criteria = Criteria.where(QUERYFIELD1).in(listOfIds)
.andOperator(Criteria.where(QUERYFIELD2).gte(ChildDate));
Query query = Query.query(criteria).noCursorTimeout();
reactiveMongoTemplate.find(query, Document.class, COLLECTIONNAME);
Where QUERYFIELD1 is "ChildId" and QUERYFIELD2 is a "ChildDate". Following is the structure of my document:
{
"_id": {
"$oid": "6296968fa63757a93e1cd123"
},
"Level1": {
"Level2": [
{
"ChildId": "1234",
"ChildDate": {
"$date": "2021-04-01T04:00:00.000Z"
}
},
{
"ChildId": "5678",
"ChildDate": {
"$date": "2017-05-16T04:00:00.000Z"
}
},
{
"ChildId": "3456",
"ChildDate": {
"$date": "2008-09-16T04:00:00.000Z"
}
},
{
"ChildDate": {
"$date": "2022-06-01T04:00:00.000Z"
},
"ChildId": "7891"
}
]
}
}
I am trying to find a document which should match the criteria within the Objects under Level2. For e.g. if My criteria has ChildId as "3456" and ChildDate as "2022-06-01T04:00:00.000Z" then I should get empty results as ChildId is matching with Object3 and ChildDate is matching with Object4. But when I use below query, I get 1 record as the match:
{ "Level1.Level2.ChildId" : "3456", "Level1.Level2.ChildDate" : { $gt: new Date("2022-01-01T05:00:00.000+00:00")}}
I am trying to achieve this using Spring Reactive MongoDB. Please help.
You can use $elemMatch for finding the documents that their array includes an item which matches the conditions:
db.collection.find({
"Level1.Level2": {
$elemMatch: {
ChildId: "3456",
ChildDate: {$gt: new Date("2008-09-16T05:00:00.000Z")}
}
}
})
See how it works on the playground example

Mongodb java driver how to use filter in projection

I have this query which works fine in mongodb:
db.document.aggregate([
{$match: { $and: [
{ type:ObjectId('abc') },
{ metadata: { $elemMatch: { metadataType: ObjectId("abc"), value: DBRef("tag", ObjectId("abc"))}}},
{ metadata: { $elemMatch: { metadataType: ObjectId("abc"), value: "abc"}}}
] }
},
{$project: {
metadata: {
$filter: {
input: "$metadata",
as: "metadata",
cond: { $or: [
{$eq: [ "$$metadata.metadataType", ObjectId("abc") ] },
{$eq: [ "$$metadata.metadataType", ObjectId("abc") ] }]}
}
}
}
How could I do this in JAVA using mongodb driver? I can do the $match stage but I don't know how to do the $project stage using $filter for the array.
I could use Document.parse() (like suggested here: https://groups.google.com/forum/#!topic/mongodb-user/W4wiPYk0Gec ) but I think it's pretty ugly.
Or I could use something like here: Does Spring Data MongoDb support $filter array aggregations operator? But it's also not too pretty.
Basically my question is: is there a good/better way to do this in the newer versions of mongodb driver? (Something like the 3rd answer here for spring mongodb: Query a document and all of its subdocuments that match a condition in mongodb (using spring) )
***** EDIT
More precisely:
Is there a way to do it like the match part here:
AggregateIterable<Document> output = document.aggregate(
Arrays.asList(
Aggregates.match(and(
eq("type", new ObjectId("abc")),
elemMatch("metadata", and(eq("metadataType", language), eq("value", abc))),
elemMatch("metadata", and(eq("metadataType", workClusterName), eq("value", "abc")))
)),
Aggregates.project(filterHOWTO)
));
You can do it with spring's MongoTemplate using MongoTemplate#aggregate and passing it a spring org.springframework.data.mongodb.core.query.Query object, which you build via a org.springframework.data.mongodb.core.query.Criteria builder pattern. The Criteria object has methods for elemMatch, etc. There are methods in Criteria for every aspect of the query you've posted here -- you just need to learn how to chain them together in the Criteria builder.

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)
)

Mongodb query array element meet criteria

I have a document like this:
solution:{
"name":"solution name",
"desc":"description",
"case":[
{
"A":13,
"B":"aaaa"
},
{
"A":14,
"B":"aaaa"
},
{
"A":13,
"B":"bbbb"
}
]
}
The case is an array field in solution table, and it contains two fields, field A and filed B.
Now I can query the solution record, and the return result will include all case elements. Now I wish the query result only includes case elements which filed B is "aaaa", how to write the query in java or MongoDB?
My expected query result should be like this:
solution:{
"name":"solution name",
"desc":"description",
"case":[
{
"A":13,
"B":"aaaa"
},
{
"A":14,
"B":"aaaa"
},
]
}
You can use aggregate pipeline $redact to keep only matched objects,
Mongo shell command,
db.solution.aggregate([ {$redact: {
"$cond": [{
"$eq": [{
"$ifNull": ["$B", "aaaa"]
},
"aaaa"
]
}, "$$DESCEND", "$$PRUNE"]
}}]).pretty()
Mongo Java code,
MongoClient client = new MongoClient("localhost");
MongoDatabase db = client.getDatabase("Test");
MongoCollection<Document> collection = db.getCollection("solution");
List<Document> results = collection.aggregate(Arrays.asList(
new Document("$redact", new Document("$cond",
Arrays.asList(new Document("$eq",
Arrays.asList(new Document("$ifNull", Arrays.asList("$B", "aaaa")), "aaaa")),
"$$DESCEND", "$$PRUNE")))
)).into(new ArrayList<Document>());
for(Document docs: results){
System.out.println(docs.toJson());
}
You may use $filter aggregation to filter an array based on a condition during projection
db.solution.aggregate([
{$match: {'case.B': "aaaa"}},
{$project: {
case: {$filter: {
input: '$case',
as: 'case',
cond: {$eq: ['$$case.B', "aaaa"]}
}},
name:1,
desc:1
}}
])

Complex MongoDB find queries on large documents in Java

How do I make a MongoDB query using BasicDBObjects in Java, when I wish to find all documents that contain an array of nested documents, where one of those nested documents meets all the specified criteria?
Taking the example data:
[
{
"_id":"blood_0",
"type":"O",
"list":[
{
"firstname":"John",
"lastname":"Smith",
"zipcode":"12345"
},
{
"firstname":"John",
"lastname":"Hamilton",
"zipcode":"54627"
},
{
"firstname":"Ben",
"lastname":"Brick",
"zipcode":"12345"
},
{
"firstname":"William",
"lastname":"Tell",
"zipcode":"15487"
}
]
},
{
"_id":"blood_1",
"type":"AB",
"list":[
{
"firstname":"Mary",
"lastname":"Smith",
"zipcode":"12345"
},
{
"firstname":"John",
"lastname":"Henry",
"zipcode":"54624"
},
{
"firstname":"Jacob",
"lastname":"Tell",
"zipcode":"19283"
},
{
"firstname":"William",
"lastname":"Dirk",
"zipcode":"15999"
}
]
}
]
If I only want to return the objects that contain a contact in the list that meets the criteria of firstname = William, lastname = Tell how would I go about doing that? The queries I am doing are not grouping the criteria, so I would get two results where I actually only should be getting one.
How would I do the same query but also checking for type = AB, as well as the other criteria, which would return no results?
You are looking for the $elemMatch operator. It restricts the query operators to a single element within the array of values.
In the shell your query will look like:
db.people.find( { list : { $elemMatch : { lastName:"Smith", firstName: "John" } } } )
To add the blood type:
db.people.find( {
type : "AB",
list : { $elemMatch : { lastName:"Smith", firstName: "John" } }
} )
This gets a bit verbose using the Java Driver.
DBObject elemMatch = new BasicDBObject();
elemMatch.put("lastName","Smith");
elemMatch.put("firstName","John");
DBObject query = new BasicDBObject();
query.append( "type", "AB");
query.append( "list", elemMatch);
Pass that query to one of the find() methods on the collection and you should get the documents you are looking for.
Note that the $elemMatch query operator will return the entire document, including all of the elements in the array. There is a similarly named projection operator to limit the array elements returned to only those matched.
HTH - Rob.
First things first. I really think your model is utterly wrong. Nested arrays which potentially grow indefinetly are bad for multiple reasons:
If the document exceeds it's padding when new members are written to this array, the document needs to be migrated within a data file. That is a pretty costly operation and you want to prevent it as much as you can.
BSON documents are limited to 16MB. So per blood type you could only have a limited number of people.
All queries tend to be a bit more complicated, the according code more bloated and hence slower.
So how to do it? Take these documents:
{
_id: ObjectId(),
firstName: "Mary",
lastName: "Smith",
zip: "12345",
bt: "AB"
},
{
_id: ObjectId(),
firstName: "John",
lastName: "Smith",
zip: "12345",
bt: "0"
}
With indices set like
db.people.ensureIndex({lastName:1,firstName:1})
db.people.ensureIndex({bt:1})
on the MongoDB shell, you can get what you want with
db.people.find({ lastName:"Smith", firstName: "John"})
or
db.people.find({ bt: "AB" })
This query for example translates to the following
MongoClient client = new MongoClient("localhost");
DB db = client.getDB("yourDB");
DBCollection coll = db.getCollection("yourCollection");
BasicDBOBject query = new BasicDBObject("bt","AB");
DBCursor cursor = coll.find(query);
try {
while( cursor.hasNext() ) {
System.out.println( cursor.next() );
}
} finally {
cursor.close();
}
You might find the MongoDB introduction for working with a Java driver interesting.

Categories

Resources