How to query MongoDb subdocuments in Java - java

Dear MongoDb Experts out there! I am new to Mongo and I am currently working with a MongoDb with Java.
Having a MongoDb with a collection called "teams" with the following example structure:
{[
{
"id": "123",
"name": "Dev1",
"employees": [
{"name": "John", "age": 30},
{"name": "Jane", "age": 30}
]
},
{
"id": "456",
"name": "Dev2",
"employees": [
{"name": "Mike", "age": 30},
{"name": "Oscar", "age": 27}
]
}
]}
I want to have a query which returns an array with all employees, that are 30 years old. So the expected result would be:
{[
{"name": "John", "age": 30},
{"name": "Jane", "age": 30},
{"name": "Mike", "age": 30}
]}
It would be even better to only get the employees name (since I know the age I searched for), like:
{[
{"name": "John"},
{"name": "Jane"},
{"name": "Mike"}
]}
I have a MongoCollection object:
MongoCollection<Document> collection = mongoClient
.getDatabase(databaseName)
.getCollection("teams")
My question: Is it possible to retrieve my expected result from the MongoDb? If so, which operations do I have to call on my collection object?

Approach 1: Find with $elemMatch projection
db.getCollection('teams').find(
{"employees.age":30},
{ "employees": { "$elemMatch": { "age": 30 } },"_id":0 }
)
Output Format:
{
"employees" : [
{
"name" : "John",
"age" : 30.0
}
]
}
{
"employees" : [
{
"name" : "Mike",
"age" : 30.0
}
]
}
Approach 2: Aggregate with $unwind and key renaming using $projection
db.getCollection('teams').aggregate([
{"$match": {"employees.age":30}} ,
{"$unwind": "$employees"},
{"$match": {"employees.age":30}} ,
{"$project": {"name": "$employees.name","_id":0}}
])
Output format:
{
"name" : "John"
}
{
"name" : "Jane"
}
{
"name" : "Mike"
}
Approach 1 would be faster, but require additional work at app layer. It is recommended to offload formatting tasks to app servers rather than on db itself.
Approach 2 can instantly give to the formatted results, doing the querying and formatting both in the database servers

Related

Mongo query with multiple filters on same attribute doesn't work with JAVA

I'm new to mongoDb and created a query that fulfills my functional requirements:
db.collection.find(
{
"typeD": "ABC"
, "typeT": {$size: 2}
, "typeT": { $all: ["def", "abc"] }
, "typeC": { $size: 3}
, "typeC": { $all: ["pdf", "video", "png"] }
, "properties": {$size: 3}
,"properties":
{
$all:
[
{"$elemMatch": {"name": "propName1", "value": "proName1_value"} }
, {"$elemMatch": {"name": "propName2", "value": "proName2_value"} }
, {"$elemMatch": {"name": "propName3", "value": "proName3_value"} }
]
}
);
I want to find the documents that exactly contains the elements provided by the arrays - as a fixed order of the elements inside arrays cannot be assumed, I've chosen the $all operator and to ensure exact matching I added to additional restriction with the $size.
Above query can be executed on mongo shell without any problems.
While trying to execute this statement with java by using mongoTemplate, I get some problems:
BasicQuery query = new BasicQuery(queryString);
CollectionEntity existingCmc = this.mongoTemplate.find(query, CollectionEntity.class);
After the first java line, query.toString() provides:
db.collection.find(
{
"typeD": "ABC"
, "typeT": { $all: ["def", "abc"] }
, "typeC": { $all: ["pdf", "video", "png"] }
,"properties":
{
$all:
[
{"$elemMatch": {"name": "propName1", "value": "proName1_value"} }
, {"$elemMatch": {"name": "propName2", "value": "proName2_value"} }
, {"$elemMatch": {"name": "propName3", "value": "proName3_value"} }
]
}
);
How can I execute the query that fulfills all of my requirements?
Can I rewrite the query so that one "single condition per attribute" is in the query?
How can I tell mongoTemplate, not to "overwrite" the previous condition for this attribute?
Thanks in advance
MongoDB queries are actually Maps (Key-Value pairs).
Since you are defining 'properties' key two times which is why it is getting overridden.
Your desired query can be done using '$and' operator.
{ "$and": [
{ "properties": { "$size": 3 } },
{ "properties": { $all:
[
{"$elemMatch": {"name": "propName1", "value": "proName1_value"} },
{"$elemMatch": {"name": "propName2", "value": "proName2_value"} },
{"$elemMatch": {"name": "propName3", "value": "proName3_value"} }
]
} }
] }

How to project array element field in Spring Data Mongo DB Aggregation

How to project embedded array element field in Spring Data MongoDB Aggregation with the document sample below, I tried:
project("customers.id")
project("customers.[].id")
project("customers.?.id")
project("$customers.id")
but didn't work.
Result document without projection:
{
"id": "group1",
"name": "Default Identity Management",
"warningThreshold": 900000,
"tariffId": "TR_0001",
"active": false,
"customers": [
{
"id": "1",
"name": "David",
"properties": [
{
"name": "phone",
"value": "678"
}
],
"roles": [
"dev"
]
},
{
"id": "2",
"name": "Peter",
"properties": [
{
"name": "phone",
"value": "770"
}
],
"roles": [
"techsales",
"dev"
]
}
]
}
Expected document like this:
{
"id" : "group1",
"name" : "Group1",
"tariffId" : "TR_0001",
"warningThreshold" : 900000,
"customers" : [
{
"id" : "1",
"name" : "David",
"properties" : [
{
"name" : "phone",
"value" : "678"
}
]
},
{
"id" : "2",
"name" : "Peter",
"properties" : [
{
"name" : "phone",
"value" : "770"
}
]
}
]
}
I would like to include customers[].id, customers[].name, customers[].properties.
I'd been trying to figure this out for a while now, but couldn't. And the other posts here on stackoverflow, and other places on the internet, didn't provide the solution I was looking for.
My problem was similar to the original author's: There's a document, which has a field which is an array of documents. I wanted to query all the top level fields in the document, and exclude a single field from the documents within the array.
s7vr's answer in the comments for the question did the job for me! Just re-posting that here since most people don't go through all the comments, and it is a really useful answer, that saved me from writing a lot of crappy code! :D
AggregationOperation project = new AggregationOperation() {
#Override
public Document toDocument(AggregationOperationContext aggregationOperationContext) {
return new Document("$project", new Document("arrayField.nestedFieldToExclude", 0));
}
};
With Lambda:
AggregationOperation project = aggregationOperationContext -> new Document("$project", new Document("arrayField.nestedFieldToExclude", 0));
Overall pipeline:
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(criteria),
Aggregation.sort(Sort.Direction.DESC, "createdOn"),
project);
I just wish there was a cleaner way to do this with the Spring MongoDB Data API directly, rather than using it this way with lambda functions.
Also, please note that the method AggregationOperation.toDocument(AggregationOperationContext aggregationOperationContext) has been deprecated as of spring-data-mongodb version 2.2.

MongoDB Java Driver aggregation with regex filter

I am using MongoDB Java Driver 3.6.3.
I want to create regex query with group by aggregation to retrieve distinct values.
Let's say I have json:
[{
"name": "John Snow",
"category": 1
},
{
"name": "Jason Statham",
"category": 2
},
{
"name": "John Lennon",
"category": 2
},
{
"name": "John Snow",
"category": 3
}]
I want to create query where regex is like "John.*" and group it by name so there would be only one "John Snow"
Expected result is:
[{
"name": "John Snow",
"category": 1
},
{
"name": "John Lennon",
"category": 2
}]
The answer provided by felix is correct, in terms of Mongo Shell commands. The equivalent expression of that command using the MongoDB Java driver is:
MongoClient mongoClient = ...;
MongoCollection<Document> collection = mongoClient.getDatabase("...").getCollection("...");
AggregateIterable<Document> documents = collection.aggregate(Arrays.asList(
// Java equivalent of the $match stage
Aggregates.match(Filters.regex("name", "John")),
// Java equivalent of the $group stage
Aggregates.group("$name", Accumulators.first("category", "$category"))
));
for (Document document : documents) {
System.out.println(document.toJson());
}
The above code will print out:
{ "_id" : "John Lennon", "category" : 2 }
{ "_id" : "John Snow", "category" : 1 }
You can achieve this with a $regex in $match stage, followed by a $group stage:
db.collection.aggregate([{
"$match": {
"name": {
"$regex": "john",
"$options": "i"
}
}
}, {
"$group": {
"_id": "$name",
"category": {
"$first": "$category"
}
}
}])
output:
[
{
"_id": "John Lennon",
"category": 2
},
{
"_id": "John Snow",
"category": 1
}
]
you can try it here: mongoplayground.net/p/evw6DP_574r
You can use Spring Data Mongo
like this
Aggregation agg = Aggregation.newAggregation(
ggregation.match(ctr.orOperator(Criteria.where("name").regex("john", "i")),
Aggregation.group("name", "category")
);
AggregationResults<CatalogNoArray> aggResults = mongoTemp.aggregate(agg, "demo",demo.class);

CosmoDB querying a value within an array using Mongo driver does not seem possible

We are migrating from mongoDB to CosmoDB using the Mongo Java Client. We have encountered the following difference in query behavior with arrays.
We have documents that look something like the following
[{
"name":"garry",
"pets":["cats","dogs"]
},
{
"name":"sally",
"pets":["cats","fish"]
}]
using mongo a query for
find({"pets":"cats"})
will return garry and sally however using cosmoDB we get zero results.
Is there a way to modify the query to replicate the same mongo behavior?
We also have documents that look something like the following that we query on type
[{
"name": "garry",
"pets": [{
"type": "cat",
"name": "Mittens"
}, {
"type": "dog",
"name": "Max"
}]
},
{
"name": "sally",
"pets": [{
"type": "cat",
"name": "Paul"
}, {
"type": "fish",
"name": "Bubbles"
}]
}
]
current mongo queries look like
find({"pets.type": "fish"})
in
I tried to use the query below, it works.
Using MongoShell.
find({"pets": {$all: ["cats"]}})
For Mongo Java Driver
FindIterable<Document> queryResult = collection.find(Filters.all("pets", "cats"));

How to insert a bulk data seperatly into mongoDB?

I have a json file that contains three datas together. I want to insert all three datas seperatly into the mongodB. Is that possible? if yes then how?
{
"docs": [
{
"_id": "First",
"count": 4,
"name": "Fish",
},
{
"_id": "Second",
"count": 6,
"name": "Meat"
},
{
"_id": "Third",
"count": 8,
"name": "Vegetables"
}
]
}
Inserting a group of documents from the mongo client shell:
let,
var input = {
"docs": [
{
"_id": "First",
"count": 4,
"name": "Fish",
},
{
"_id": "Second",
"count": 6,
"name": "Meat"
},
{
"_id": "Third",
"count": 8,
"name": "Vegetables"
}
]
}
Inserting the docs array:
db.collection.insert(input["docs"]);
This would insert each item in the docs array as separate documents in the collection.
db.collection.find();
would give us, three different documents that were inserted.
{ "_id" : "First", "count" : 4, "name" : "Fish" }
{ "_id" : "Second", "count" : 6, "name" : "Meat" }
{ "_id" : "Third", "count" : 8, "name" : "Vegetables" }
To do it in Java, you need to load and parse the JSON file using JSON parsing libraries such as Jackson parser, get the docs array and persist it.

Categories