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()));
}
Related
I am attempting a simple projection using Spring Data Mongo's Aggregation API.
The pipeline step I want to do is:
{
$project : {
"account._id" : 1,
"account.position" : 1
}
}
This is what I have tried (along with a ton of other tweaks because nothing seems to work):
ProjectionOperation project1 = Aggregation.project("account._id", "account.position");
However, even though this is how the documentation says to do it here: https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo.aggregation.projection
The actual document that is rendered by that projection ends up looking like:
{
$project : {
_id : "$account._id",
position : "$account.position"
}
}
Which works completely differently than the projection that I am wanting to use.
Does anyone know how to get a projection like I want out of Spring Data Mongo Aggregation API, or is this a bug I need to report?
Update 8/29/2019 - Adding more data to build out the context:
Two collections are involved: "groups" and "accounts"
A group looks something like this:
{
_id : ObjectId("..."),
name: ...,
ownerId: ObjectId("..."),
other stuff...
}
An account looks something like this:
{
_id : ObjectId("..."),
position : "ABC",
memberships : [{
groupId: ObjectId("..."),
otherstuff: ...,
}],
other stuff...
}
My whole aggregation looks like this and works as desired in mongodb shell: (trying to get a list of all account ids of a particular type that are members of any groups owned by a particular user)
groups.aggregate(
{
$match : {
ownerId : ObjectId("XYZ"),
}
},
{
$lookup: {
from: "accounts",
localField: "_id",
foreignField: "memberships.groupId",
as: "account"
}
},
{
$project: {
"account._id" : 1,
"account.position" : 1
}
},
{
$unwind: "$account"
},
{
$match: {
"account.position" : "ZZZ"
}
},
{
$project: {
_id : 0,
accountId : "$account._id"
}
})
Java version of the Aggregation:
MatchOperation match1 = Aggregation.match(
where("ownerId").is(accountId));
LookupOperation lookupOperation = LookupOperation.newLookup()
.from("accounts")
.localField("_id")
.foreignField("memberships.groupId")
.as("account");
// This doesn't work correctly on nested fields:
ProjectionOperation project1 = Aggregation.project(
"studentAccount._id",
"studentAccount.position");
Aggregation aggregation = Aggregation.newAggregation(
match1,
lookupOperation,
project1,
unwind("account"),
match(where("account.position").is("ZZZ")),
project().and("account._id").as("accountId"));
If you want your aggregation work look like mongoshell your could try like this
Aggregation aggregation = Aggregation.newAggregation(
match1,
lookupOperation,
// This's your project operation
new AggregationOperation() {
#Override
public Document toDocument(AggregationOperationContext aggregationOperationContext) {
Document project = new Document("$project",
new Document(
"_id", "$account._id"
).append("position", "$account.position")
);
return aggregationOperationContext.getMappedObject(project);
}
},
unwind("account"),
match(where("account.position").is("ZZZ")),
project().and("account._id").as("accountId")
);
You can check my answer here in a more generic way
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();
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.
We are inserting mongo documents with identifier and there is a subarray within the documents.
insert 1 :
db.test.insert(
{
"companyId" : "123",
"persons" : [
{
"joiningDate" : NumberLong("1431674741623"),
"name" : "Rajesh"
}
],
})
insert 2 :
db.test.insert(
{
"companyId" : "123",
"persons" : [
{
"joiningDate" : NumberLong("1431674741653"),
"name" : "Rahul"
}
],
})
I would like to retreive the company details based on the company id and merge the persons into one array list, and sort the persons based on the joining date.
Currently I am able to retreive the data using QueryBuilder but I am unable to sort the person based on the date.I can use java comparator to do the same , but I am looking out if there is any API from mongo db java driver which can be used to get the same.
Thanks.
you should use mongo aggregation like first $unwind persons array and then sort persons.joiningDate and then group with push as below :
db.test.aggregate({
"$match": {
"companyId": "123" // match companyId
}
}, {
"$unwind": "$persons" //unwind person array
}, {
"$sort": {
"persons.joiningDate": -1 //sort joining date
}
}, {
"$group": {
"_id": "$companyId",
"persons": {
"$push": "$persons" //push all sorted data into persons
}
}
}).pretty()
For converting this code into java use mongo java aggregation as
// unwind persons
DBObject unwind = new BasicDBObject("$unwind", "$persons");
// create pipeline operations, with the $match companyId
DBObject match = new BasicDBObject("$match", new BasicDBObject("companyId", "123"));
// sort persons by joining date
DBObject sortDates = new BasicDBObject("$sort", new BasicDBObject("persons.joiningDate", -1)); // -1 and 1 descending or ascending resp.
// Now the $group operation
DBObject groupFields = new BasicDBObject("_id", "$companyId");
groupFields.put("persons", new BasicDBObject("$push", "$persons"));
DBObject group = new BasicDBObject("$group", groupFields);
// run aggregation
List < DBObject > pipeline = Arrays.asList(match, unwind,sortDates, group);
AggregationOutput output = test.aggregate(pipeline);
for(DBObject result: output.results()) {
System.out.println(result);
}
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;
}