Mongodb query array element meet criteria - java

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

Related

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

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

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

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 } } }
] );

How to retrieve / find all elements of a nested array in MongoDB Java

I am trying to find / load all elements in the "nodes" array into a Java List or Hashmap.
I'm dealing with a specific JSON format that I cannot modify. The Mongo DB collection contains only one document, and that document is shown below. I am trying to query all elements of the "nodes" array but can't manage to do so.
MongoCollection<Document> collection = mongoDB.getCollection(collectionName);
BasicDBObject query = new BasicDBObject();
query.put("nodes", "");
List<Document> test2 = collection.find(query).into(new ArrayList<Document>());
Test2 returns NULL at the moment. I know I'm wrong but can't figure out how to do it.
And here is the JSON
{
"_id": "12123434",
"nodes": [
{
"id": "1",
"name": "bla",
"attributes": [
"string1",
"string2"
]
},
{
"id": "2",
"name": "blabla",
"attributes": [
"string1",
"string2"
]
}
],
"groups": []
}
You just need to project nodes and map.
import static com.mongodb.client.model.Projections.*;
List<Document> nodes = (List<Document>) collection.find().projection(fields(include("nodes"), excludeId())).map(document -> document.get("nodes")).first();
Thank you Sagar, your code snippet helped me a lot.
Just in case if someone wants to target the filtering on a field (ex - id) within the nodes document then the following can be used.
List<Document> nodes = (List<Document>) collection.find().projection(elemMatch("nodes",new Document("id",2))).map(document -> document.get("nodes")).first();

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