Find objects by array in mongodb (or java) - mongodb

I've got a collection (dataset) like this:
{
"_id" : ObjectId("515611c1c6e3718ee42a5655"),
"id": "Product1",
"type": "ProductType4"
"productFeature": [
{
"id": "ProductFeature1"
},
{
"id": "ProductFeature2"
},
{
"id": "ProductFeature3"
}
]
"productPropertyNumeric": 25
},
... and more product objects...
{
"_id" : ObjectId("515611c1c6e3718ee42a5666"),
"id": "ProductFeature1",
"label": "blablabla"
},
{
"_id" : ObjectId("515611c1c6e3718ee42a5667"),
"id": "ProductFeature2",
"label": "blebleble"
},
{
"_id" : ObjectId("515611c1c6e3718ee42a5668"),
"id": "ProductFeature3",
"label": "blublublu"
} ... and more feature objects...
According to Product1, I have to find the features and labels that the specific product has in its "productFeature" array.
I have tried in Mongo shell to find them (using a variable, for example):
var aaa = db.dataset.find({ id: "Product1" })
db.dataset.find({ id: "aaa.productFeature.id" })
But it doesn't work. If somebody knows how to find objects by array please help me.
Thanks very much.
PS: It would be best in Java - I apply a query just for example:
BasicDBObject query = new BasicDBObject();
query.put("type","ProductType4");
query.put("productPropertyNumeric", new BasicDBObject("$gt", 10));
DBCursor cursor = coll.find(query).sort( new BasicDBObject("label", 1));
while (cursor.hasNext()){
System.out.println(cursor.next().get("id"));
}

Here is my answer to my own question. I hope this helps to someone.
BasicDBObject query = new BasicDBObject();
BasicDBObject field = new BasicDBObject();
query.put("id", "Product1");
field.put("id", 1);
field.put("productFeature", 1);
field.put("_id", 0);
DBCursor cursor = coll.find(query, field);
while (cursor.hasNext()) {
BasicDBObject result = (BasicDBObject) cursor.next();
System.out.println(result);
ArrayList<BasicDBObject> features = (ArrayList<BasicDBObject>) result.get("productFeature");
for (BasicDBObject embedded : features) {
String featuresId = (String) embedded.get("id");
BasicDBObject query2 = new BasicDBObject();
BasicDBObject field2 = new BasicDBObject();
query2.put("id", featuresId);
field2.put("id", 1);
field2.put("label", 1);
field2.put("_id", 0);
DBCursor cursor2 = coll.find(query2, field2);
while (cursor2.hasNext()) {
System.out.println(cursor2.next());
}
}
}

You have to supply the "path" in the document structure to the field you want to query on from the document root. In this case the path is 'productFeature' --> 'id'. Instead of an arrow MongoDB uses a dot (.), e.g.,
db.dataset.find({ "productFeature.id" : "Product1" });
In Java you do something very similar:
BasicDBObject query = new BasicDBObject("productFeature.id" : "Product1");
DBCursor cursor = coll.find(query).sort( new BasicDBObject("label", 1));
while (cursor.hasNext()){
System.out.println(cursor.next().get("id"));
}

In Java you could also use the Query class in combination with MongoTemplate.
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
#Autowired
private final MongoTemplate mongoTemplate;
...
public YourObjectClass findProduct1(){
Query query = new Query();
query.addCriteria(Criteria.where("productFeature.id").is("Product1"));
List<YourObjectClass> result = this.mongoTemplate.find(query, YourObjectClass.class);
return result;
}

Related

Springboot monodb query for to filtered data count

Let say i have userInformation collection below structure
userInformation:
- name : String
- accounts[] : array
I need to covert below query into java springboot query.
db.userInformation.aggregate([{$match : {"name" : "madhu"}}, {$project: { "name": 1,
accountsCount: { $size:{ $filter: { input: "$accounts", as: "account", cond: {"$eq":["$$account.enabled", 0]}}}} }}]);
I have tried like below:
MatchOperation matchStage = Aggregation.match(new Criteria("name").is("madhu");
ProjectionOperation appProjectStage = Aggregation.project("name").
and(new AggregationExpression() {
#Override
public Document toDocument(AggregationOperationContext context) {
Document filterExpression = new Document();
filterExpression.put("input", "$accounts");
filterExpression.put("as", "account");
filterExpression.put("cond", new Document("$eq", Arrays.<Object> asList("$$account.enabled", 1)));
return new Document("$filter", filterExpression);
}
}).as("filterdAccounts");
From there I could not able to convert that as count. Any help is appropriated.
Thanks
You should try using ArrayOperators like this
Aggregation.project("name").and(ArrayOperators.Size.lengthOfArray(ArrayOperators.Filter.filter("$accounts").as("account").by(ComparisonOperators.Eq.valueOf("account.enabled").equalToValue(1)))).as("filterdAccounts");

Mongo query to filter inner Arraylist items in Spring Boot using Mongo Template

Below is my DOCUMENT:
#Document(collection = "products")
#Data
#EqualsAndHashCode
public class Product {
#Id
private String id;
#Field("lang_content_list")
private List<ProductLangContent> contentList;
#Data
public static class ProductLangContent {
#Field("lang")
private String lang;
}
}
I want to get only those contentList where
lang = 'en'. lang is unique within innner list.
Note: I am using Mongotemplate
My sample json is:
{
"_id" : ObjectId("5d2040f9f7c5ac1e9d8ef712"),
"lang_content_list" : [
{
"lang" : "en"
},
{
"lang" : "np"
}
]
"_class" : "com.sn.application.model.Product"
}
Desired query result is:
{
"_id" : ObjectId("5d2040f9f7c5ac1e9d8ef712"),
"lang_content_list" : [
{
"lang" : "en"
}
]
}
I tried couple of queries but got no luck:
Aggregation aggregation = newAggregation(
project().and(filter("contentList")
.as("item")
.by(valueOf(
"item.lang")
.equalToValue(
"en")))
.as("contentList")
);
List<Product> results = mongoTemplate.aggregate(aggregation, Product.class, Product.class).getMappedResults();
output is: contentList is null.
Tried:
Criteria elementMatchCriteria = Criteria.where("contentList").elemMatch(Criteria.where("lang").is("en"));
It gives all elements in contentList. I don't want that. I want only one object in inner list where lan = 'en' .
Huge Thank you in advance.
Tried:
AggregationOperation match = Aggregation.match(Criteria.where("contentList.lang").is("en"));
AggregationOperation unwind = Aggregation.unwind("contentList");
AggregationOperation group = Aggregation.group("id")
.push("contentList").as("contentList");
List<AggregationOperation> operations = new ArrayList<>();
operations.add(match);
operations.add(unwind);
operations.add(match);
operations.add(group);
Aggregation aggregation = Aggregation.newAggregation(operations);
List<Product> results = mongoTemplate.aggregate(aggregation, Product.class, Product.class).getMappedResults();
System.out.println(results.get(0).getContentList() != null);
output is: false. Inner array object is coming as null.
Your document has an array field "contentList" which will be having multiple "lang". I'm assuming you want to filter/get all those documents in which atleast one "lang" in "contentList" is "en". Then use :
Criteria elementMatchCriteria = Criteria.where("contentList.lang").is("en"));
If you want only that object in the inner array where lang='en', you need to use aggregation pipeline like:
Link: https://mongoplayground.net/p/JaJ7420i4qJ
db.collection.aggregate([
{
$match: {
"lang_content_list.lang": "en"
}
},
{
$unwind: "$lang_content_list"
},
{
$match: {
"lang_content_list.lang": "en"
}
},
{
$group: {
"_id": "$_id",
"_class": {
$first: "$_class"
},
"lang_content_list": {
$push: "$lang_content_list"
}
}
}
])
The reason for using the last group stage is that in your object, contentList is an array so we need to wrap lang object as array, otherwise not needed if you can change return type object.
In Spring MongoTemplate code:
AggregationOperation match = Aggregation.match(Criteria.where("lang_content_list.lang").is("en"));
AggregationOperation unwind = Aggregation.unwind("lang_content_list");
AggregationOperation group = Aggregation.group("_id")
.first("_class").as("_class")
.push("lang_content_list").as("lang_content_list");
List<AggregationOperation> operations = new ArrayList<>();
operations.add(match);
operations.add(unwind);
operations.add(match);
operations.add(group);
Aggregation aggregation = Aggregation.newAggregation(operations);
List<Product> results = mongoTemplate.aggregate(aggregation, Product.class, Product.class).getMappedResults();

modify array element's value with Java MongoDB driver

I want to modify array element using Java MongoDB driver. I am able to insert new pair to the array, but can't modify value corresponding to particular key.
How can I increase a by 2 in dummy array for document
{ "_id" : ObjectId("57a87614d03a435e4be44bb9"), "dummy" : [ { "a" : 1 }, { "b" : 5 } ] }
using Java MongoDB driver?
Here is what I've tried
BasicDBObject query = new BasicDBObject();
query.put("_id",doc_id_here);
BasicDBObject incValue = new BasicDBObject("dummy.$.a", 1);
BasicDBObject intModifier = new BasicDBObject("$inc", incValue);
coll.update(query, intModifier, false, false, WriteConcern.SAFE);
Your query in mongo shell
db.collection.update(
{ "_id": ObjectId("57a87614d03a435e4be44bb9") },
{ $inc: { "dummy.$.a": 1 } }
);
will result in error
The positional operator did not find the match needed from the query.
Unexpanded update: dummy.$.a
because in order to use positional $ operator for dummy array you need to set condition on this array in your query
db.collection.update(
{ "_id": ObjectId("57a87614d03a435e4be44bb9"), "dummy.a": { $exists: true } },
{ $inc: { "dummy.$.a": 1 } }
);
and then it will increment a as you expect.
With Java MongoDB driver it will be
BasicDBObject query = new BasicDBObject();
query.put("_id", new ObjectId("57a87614d03a435e4be44bb9"));
query.put("dummy.a", new BasicDBObject("$exists", true));
BasicDBObject incValue = new BasicDBObject("dummy.$.a", 1);
BasicDBObject intModifier = new BasicDBObject("$inc", incValue);
coll.update(query, intModifier, false, false, WriteConcern.SAFE);
Advice: before trying to construct your query with Java MongoDB Driver first try if it works in mongo shell.

MongoDB shell aggregation does not work in Morphia

Consider the following MongoDB collection:
{
"_id" : ObjectId("..."),
"myId": 12345,
"root": {
basicData: {
code: "CODE"
}
data: [
{
descriptions: {
description: [
{
text: "...",
language: "de"
}
]
}
}
]
}}
I'm trying to get documents filtered by "myId" and "code", but with descriptions in only one specific language. In the shell, the following command seems to work properly:
db.Items.aggregate([
{ "$match" : { "myId" : 40943 , "root.basicData.code" : "A_CODE"}},
{ "$unwind" : "$root.data"},
{ "$unwind" : "$root.data.descriptions.description"},
{ "$match" : { "root.data.descriptions.description.language" : "de"}}
])
In Morphia I try to do the following to get to the same result:
AggregationPipeline pipeline = dataStore.createAggregation(Item.class);
Query<Item> matchIdAndCode = dataStore.createQuery(Item.class);
matchIdAndCode.field("myId").equal(myid);
matchIdAndCode.field("root.basicData.code").equal(code);
pipeline.match(matchIdAndCode);
pipeline.unwind("root.data");
pipeline.unwind("root.data.descriptions.description");
Query<Item> matchLanguage = dataStore.createQuery(Item.class);
matchLanguage.field("root.data.descriptions.description.language").equal(language);
pipeline.match(matchLanguage);
Iterator<Item> itemAggregate = pipeline.aggregate(Item.class);
but the iterator does not contain any items. I am not shure where to search for further errors, especially because when I copy the stages in the morphia aggregation pipeline to the shell, I get the expected result.
you are missing $ sign in following lines
pipeline.unwind("root.data");
pipeline.unwind("root.data.descriptions.description");
should be
pipeline.unwind("$root.data");
pipeline.unwind("$root.data.descriptions.description");
As a workaround, I now used the MongoDB Java Driver. My working solution:
List<DBObject> stages = new ArrayList<DBObject>();
DBCollection collection = dataStore.getCollection(Item.class);
// match
DBObject matchFields = new BasicDBObject("myId", myid);
matchFields.put("code", code);
DBObject match = new BasicDBObject("$match", matchFields );
stages.add(match);
// unwind
DBObject unwindDescriptiveData = new BasicDBObject("$unwind", "$root.data");
stages.add(unwindDescriptiveData);
DBObject unwindDescription = new BasicDBObject("$unwind", "$root.data.descriptions.description");
stages.add(unwindDescription);
// match
DBObject languageMatchFields = new BasicDBObject("root.data.descriptions.description.language", language);
DBObject languageMatch = new BasicDBObject("$match", languageMatchFields );
stages.add(languageMatch);
AggregationOutput aggregate = collection.aggregate(stages);
The mapping to the pojo can be done with Morphia again:
List<Item> items = new ArrayList<Item>();
for (Iterator<DBObject> iterator = aggregate.results().iterator(); iterator.hasNext();) {
items.add(morphia.fromDBObject(Item.class, iterator.next()));
}

update a document in mongoDB using JAVA

I'm just starting to learn mongoDB using JAVA. I have the the following document in mongo
{
"_id": {
"$oid": "513fa587c5d0cf174eb9b1f8"
},
"suitename": "test_suite_name",
"testname": "test_name]",
"milestones": [
{
"milestone_id": 45
}
]
}
I have a compound key on suitename, testname, and milestone_id. I have to process a file which has these three fields. I create a simple DBObject query and check if count != 0
BasicDBObject query = new BasicDBObject();
query.put("suitename", testsuite);
query.put("testname", testcase);
query.put("milestones.milestone_id", SampleProgram.milestone_id);
If count == 0 --> add document in mongo -> this seems to work fine
What I am trying to figure out is:
If I have a new value of milestone_id in my file, I just need to add a milestone to some existing document's milestone array. Existing document is determined based on suitename AND testname.
So if milestone_id = 10, the document should look like
{
"_id": {
"$oid": "513fa587c5d0cf174eb9b1f8"
},
"suitename": "test_suite_name",
"testname": "test_name]",
"milestones": [
{
"milestone_id": 45
},
{
"milestone_id": 10
}
]
}
How can I accomplish this?
Thanks
This can be accomplished with the $push operator which appends values to an array and the update(…) method.
BasicDBObject query = new BasicDBObject();
query.put("suitename", testsuite);
query.put("testname", testcase);
BasicDBObject push = new BasicDBObject();
push.put("$push",
new BasicDBObject("milestones",
new BasicDBObject("milestone_id", SampleProgram.milestone_id)));
yourCollection.update(query, push);
BasicDBObject newDocument = new BasicDBObject();
newDocument.append("$set", new BasicDBObject().append("clients", 110));
BasicDBObject searchQuery = new BasicDBObject().append("hosting", "hostB");
collection.update(searchQuery, newDocument);