java.lang.IllegalArgumentException: Invalid reference performing aggregation in MongoDB - java

I have a document in collection as :
{
"_id": ObjectId("5ab273ed31fa764560a912f8"),
"hourNumber": 21,
"errorSegments": [{
"agentName": "agentX"
},
{
"agentName": "agentY"
}]
}
I am trying to perform following aggregation function in spring boot where i want to retrieve "errorSegments" of a particular hour that matches an agent , which is working fine in mongo shell :
Working shell :
db.errorsegment.aggregate([{
"$match": {
"hourNumber": 21,
"errorSegments.agentName": "agentX"
}
},
{
"$project": {
"errorSegments": {
"$filter": {
"input": "$errorSegments",
"as": "e",
"cond": {
"$eq": ["$$e.agentName",
"agentX"]
}
}
}
}
},
{
"$unwind": "$errorSegments"
},
{
"$replaceRoot": {
"newRoot": "$errorSegments"
}
}])
So it provides output only , which is desired result :
{ "agentName" : "agentX" }
But my following code in spring gives error :
MatchOperation match = Aggregation.match(Criteria.where("hourNumber").is(21).and("errorSegments.agentName").is("agentX"));
ProjectionOperation project = Aggregation.project()
.and(new AggregationExpression() {
#Override
public DBObject toDbObject(AggregationOperationContext context) {
DBObject filterExpression = new BasicDBObject();
filterExpression.put("input", "$errorSegments");
filterExpression.put("as", "e");
filterExpression.put("cond", new BasicDBObject("$eq", Arrays.<Object> asList("$$e.agentName","agentX")));
return new BasicDBObject("$filter", filterExpression);
} }).as("prop");
UnwindOperation unwind = Aggregation.unwind("$errorSegments");
ReplaceRootOperation replaceRoot = Aggregation.replaceRoot("$errorSegments");
Aggregation aggregation = Aggregation.newAggregation(match,project,unwind,replaceRoot);
AggregationResults<ErrorSegment> errorSegments = mongoOps.aggregate(aggregation, SegmentAudit.class , ErrorSegment.class);
Following is the Logs :
java.lang.IllegalArgumentException: Invalid reference 'errorSegments'!
at org.springframework.data.mongodb.core.aggregation.ExposedFieldsAggregationOperationContext.getReference(ExposedFieldsAggregationOperationContext.java:99) ~[spring-data-mongodb-1.10.3.RELEASE.jar:na]
at org.springframework.data.mongodb.core.aggregation.ExposedFieldsAggregationOperationContext.getReference(ExposedFieldsAggregationOperationContext.java:71) ~[spring-data-mongodb-1.10.3.RELEASE.jar:na]
at org.springframework.data.mongodb.core.aggregation.UnwindOperation.toDBObject(UnwindOperation.java:95) ~[spring-data-mongodb-1.10.3.RELEASE.jar:na]
at org.springframework.data.mongodb.core.aggregation.AggregationOperationRenderer.toDBObject(AggregationOperationRenderer.java:56) ~[spring-data-mongodb-1.10.3.RELEASE.jar:na]
at org.springframework.data.mongodb.core.aggregation.Aggregation.toDbObject(Aggregation.java:580) ~[spring-data-mongodb-1.10.3.RELEASE.jar:na]
at org.springframework.data.mongodb.core.MongoTemplate.aggregate(MongoTemplate.java:1567) ~[spring-data-mongodb-1.10.3.RELEASE.jar:na]
at org.springframework.data.mongodb.core.MongoTemplate.aggregate(MongoTemplate.java:1502) ~[spring-data-mongodb-1.10.3.RELEASE.jar:na]

The source of error is the alias that you use with filter operation. It should be errorSegments instead of prop but you you other problems too. Use fieldname i.e $ prefix is not right.
Here is the updated aggregation. You can use $filter helper.
MatchOperation match = Aggregation.match(Criteria.where("hourNumber").is(21).and("errorSegments.agentName").is("agentX"));
ProjectionOperation project = Aggregation.
project().and(ArrayOperators.Filter.filter("errorSegments")
.as("e")
.by(ComparisonOperators.Eq.valueOf(
"e.agentName")
.equalToValue(
"agentX")))
.as("errorSegments");
UnwindOperation unwind = Aggregation.unwind("errorSegments");
ReplaceRootOperation replaceRoot = Aggregation.replaceRoot("errorSegments");
Aggregation aggregation = Aggregation.newAggregation(match,project,unwind,replaceRoot);
Below is the generated query.
[
{
"$match": {
"hourNumber": 21,
"errorSegments.agentName": "agentX"
}
},
{
"$project": {
"errorSegments": {
"$filter": {
"input": "$errorSegments",
"as": "e",
"cond": {
"$eq": [
"$$e.agentName",
"agentX"
]
}
}
}
}
},
{
"$unwind": "$errorSegments"
},
{
"$replaceRoot": {
"newRoot": "$errorSegments"
}
}
]

Related

Mongo aggregation for a DbRef field with mongoTemplate

Assume I have two mongo collections as follows.
Collection A
{
"_id" : ObjectId("582abcd85d2dfa67f44127e0"),
"level" : "super"
"dataReference" : Object
B : DbRef(B, 5b618a570550de0021aaa2ef, undefined)
}
Collection B
{
"_id" : ObjectId("5b618a570550de0021aaa2ef"),
"role" : "admin"
}
What I need is retrieve the records from Collection A, which records have "level" field's value as "super" and its related Collection B record's "role" value as "admin".
For this, I am trying to use aggregation and java mongoTemplate.
Following is the code that I tried but it returns 0 records.
final TypedAggregation<A> typedAggregation = Aggregation.newAggregation(A.class,
Aggregation.match(Criteria.where("level").equals(level)),
Aggregation.lookup("B", "_id", "dataReference.B.$id", "Basb"),
Aggregation.match(new Criteria().andOperator(
Criteria.where("B.role").regex("admin")
)));
final AggregationResults<Map> A = mongoTemplate.aggregate(typedAggregation, "A", Map.class);
Please note that I am new to Mongo aggregation.
It's quiet ugly solution:
MongoTemplate
You cannot use TypedAggregation because we need to transform A collection to be able join with B collection
Aggregation typedAggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("level").is("super")),
new AggregationOperation() {
#Override
public Document toDocument(AggregationOperationContext context) {
return Document.parse("{\"$addFields\":{\"dataReference\":{\"$reduce\":{\"input\":{\"$objectToArray\":\"$dataReference\"},\"initialValue\":null,\"in\":{\"$cond\":[{\"$eq\":[{\"$type\":\"$$this.v\"},\"objectId\"]},\"$$this.v\",\"$$value\"]}}}}}");
}
},
Aggregation.lookup("B", "dataReference", "_id", "B"),
Aggregation.match(new Criteria().andOperator(
Criteria.where("B.role").regex("admin")
)
)
);
final AggregationResults<Document> A = mongoTemplate.aggregate(typedAggregation, "A", Document.class);
MongoDB Aggregation
db.A.aggregate([
{
"$match": {
"level": "super"
}
},
{
"$addFields": {
"B": {
"$reduce": {
"input": {
"$objectToArray": "$dataReference"
},
"initialValue": null,
"in": {
"$cond": [
{
"$eq": [
{
"$type": "$$this.v"
},
"objectId"
]
},
"$$this.v",
"$$value"
]
}
}
}
}
},
{
"$lookup": {
"from": "B",
"localField": "B",
"foreignField": "_id",
"as": "B"
}
},
{
"$match": {
"$and": [
{
"B.role": {
"$regex": "admin",
"$options": ""
}
}
]
}
}
])
MongoPlayground

MongoDb Lookup gives empty aggregation result when using Spring Data Mongodb

The following is a app_relation Collection :
{
"_id" : ObjectId("5bf518bb1e9f9d2f34a8299b"),
"app_id" : "123456789",
"dev_id" : "1",
"user_id" : "1",
"status" : "active",
"created" : NumberLong(1542789294)
}
The other collection is app :
{
"_id" : ObjectId("5bd02abb1e9f9d2adc211138"),
"app_id" : "123456789",
"custom_app_name" : "Demo",
"price" : 10,
"created" : NumberLong(1540369083)
}
Using Lookup in mongodb I want to embed App collection in AppRelation
For the same my mongodb query is:
db.app_relation.aggregate([
{
$lookup: {
"from": "app",
"localField": "app_id",
"foreignField": "app_id",
"as": "data"
}
},
{
$match: {
"data": {
"$size": 1
}
}
}
])
The equivalent code in Spring Java is :
LookupOperation lookupOperation = LookupOperation.newLookup().from("app").localField("app_id")
.foreignField("app_id").as("data");
AggregationOperation match = Aggregation.match(Criteria.where("data").size(1));
Aggregation aggregation = Aggregation.newAggregation(lookupOperation, match)
.withOptions(Aggregation.newAggregationOptions().cursor(new BasicDBObject()).build());
List<AppRelation> results = mongoTemplate.aggregate(aggregation, AppRelation.class, AppRelation.class)
.getMappedResults();
When executing the above code it provides the empty collection, whereas executing mongo db query it provides proper result.
The query generated in Debug logs is:
{
"aggregate": "app_relation",
"pipeline": [
{
"$lookup": {
"from": "app",
"localField": "app_id",
"foreignField": "app_id",
"as": "data"
}
},
{
"$match": {
"data": {
"$size": 1
}
}
}
],
"cursor": {}
}

Trying to convert mongoDB aggregation query to java aggregation query

I am trying to convert MongoDB Aggregation function to Java Aggregation function.
My MongoDB Query is
[
{
"$match": {
"indicesId": "VUSSTAPNETFF"
}
},
{
"$unwind": "$dataSets"
},
{
"$match": {
"dataSets.date": {
"$lte": ISODate("2013-12-31T18:30:00.000Z"),
"$gte": ISODate("2008-12-31T18:30:00.000Z")
}
}
},
{
"$group": {
"_id": "$indicesId",
"mean": {
"$avg": "$dataSets.data"
},
"indices": {
"$push": "$$ROOT"
}
}
},
{
"$unwind": "$indices"
},
{
"$project": {
"_id": 1,
"mean": 1,
"firstResult": {
"$multiply": [
{
"$subtract": [
"$indices.dataSets.data",
"$mean"
]
},
{
"$subtract": [
"$indices.dataSets.data",
"$mean"
]
}
]
}
}
},
{
"$group":{
"_id":"",
"secondResult": {
"$avg": "$firstResult"
},
"mean":{
"$first": "$mean"
}
}
},
{
"$project":{
"data":{
"$sqrt":"$secondResult"
},
"mean": "$mean"
}
}
]
I tried bellow code for conversion in java.
BasicDBList subtractDBList= new BasicDBList();
subtractDBList.add("$indices.dataSets.data");
subtractDBList.add("$mean");
BasicDBList multiplyDBList= new BasicDBList();
multiplyDBList.add(new BasicDBObject("$subtract",subtractDBList));
multiplyDBList.add(new BasicDBObject("$subtract",subtractDBList));
Aggregation meanAggregation= Aggregation.newAggregation(Aggregation.match(Criteria.where(IndicesUtil.INDICES_ID).is(indicesId)),
Aggregation.unwind(IndicesUtil.PREFIX+IndicesUtil.DATA_SETS),
Aggregation.match(Criteria.where(IndicesUtil.DATA_SETS_DATE).gte(startDate).lte(indicesDataSet.getDate())),
Aggregation.group(IndicesUtil.INDICES_ID).avg(averageParameter).as(IndicesUtil.MEAN).push("$$ROOT").as("indices"),
Aggregation.unwind(IndicesUtil.PREFIX+IndicesUtil.INDICES),
new AggregationOperation() {
#Override
public DBObject toDBObject(AggregationOperationContext arg0) {
return new BasicDBObject("$project",
new BasicDBObject("_id",1).append("mean", IndicesUtil.PREFIX+IndicesUtil.MEAN).append("firstResult",
new BasicDBObject("$multiply",multiplyDBList)));
}
},
Aggregation.group().avg("$firstResult").as("secondResult").first(IndicesUtil.PREFIX+IndicesUtil.MEAN).as(IndicesUtil.MEAN),
new AggregationOperation() {
#Override
public DBObject toDBObject(AggregationOperationContext arg0) {
return new BasicDBObject("$project",
new BasicDBObject(IndicesUtil.DATA,
new BasicDBObject("$sqrt","$secondResult").append("mean", IndicesUtil.PREFIX+IndicesUtil.MEAN)));
}
}
);
I am getting problem with bellow line
Aggregation.group().avg("$firstResult").as("secondResult").first(IndicesUtil.PREFIX+IndicesUtil.MEAN).as(IndicesUtil.MEAN),
And error as per bellow
Invalid reference '$firstResult'!
Thanks in advance.
You can simplify your aggregation on 1.10.1-Release spring mongo version. You should avoid using BasicDBObject/Document and use spring provided helper methods.
Aggregation meanAggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where(IndicesUtil.INDICES_ID).is(indicesId)),
Aggregation.unwind(IndicesUtil.PREFIX+IndicesUtil.DATA_SETS),
Aggregation.match(Criteria.where(IndicesUtil.DATA_SETS_DATE).gte(startDate).lte(indicesDataSet.getDate())),
Aggregation.group(IndicesUtil.INDICES_ID).avg(averageParameter).as(IndicesUtil.MEAN).push("$$ROOT").as("indices"),
Aggregation.unwind(IndicesUtil.PREFIX+IndicesUtil.INDICES),
Aggregation.project("_id", "mean").andExpression("(indices.dataSets.data - mean) * (indices.dataSets.data - mean)").as("firstResult"),
Aggregation.group().avg("$firstResult").as("secondResult").first(IndicesUtil.PREFIX+IndicesUtil.MEAN).as(IndicesUtil.MEAN),
Aggregation.project("mean").and("secondResult").sqrt().as(IndicesUtil.DATA)
);

Spring mongodb: Aggregation with lookup, projection and slice

I have a collection ref consisting of
{
"_id": "refId"
"ref": "itemId"
}
and a collection item
{
"_id": "itemId"
"field1": ...
"array": [
{
"field2": "a"
},
{
"field2": "b"
}
]
}
Here i need to 'join' a.k.a perform a lookup on ref to item, and project the fields field1 and the last item of array as arrayItem into the top level of the return document, to return
{
"_id": "itemId",
"field1": ...
"arrayItem: "b"
}
Using the mongo shell, this works perfectly using the following statement:
db.ref.aggregate([
{ "$lookup": {
"from": "item",
"localField": "ref",
"foreignField": "_id",
"as": "item"
}},
{ "$unwind": "$item" },
{ "$project": {
"_id": "$item._id",
"field1": "$item.field1",
"arrayItem": { $slice: [ "$item.array", -1, 1 ]}
}},
{ "$unwind": "$arrayItem" }
])
Now, i have to make this happen in Java with Spring and Spring-MongoDB, which i have tried using this Aggregation:
Aggregation.newAggregation(
new LookupAggreationOperation("item", "ref", "_id", "item"),
Aggregation.unwind("item"),
Aggregation.project(Fields.from(
Fields.field("_id", "$item._id"),
Fields.field("field1", "$item.field1"),
Fields.field("array", "$item.array"))),
Aggregation.project("_id", "field1", "array")
.and("$array").project("slice", -1, 1).as("$arrayItem"),
Aggregation.unwind("$array"));
As lookup is only available in Spring-Mongo 1.9.2, I had to rebuild it myself like that:
public class LookupAggregationOperation implements AggregationOperation {
private DBObject operation;
public LookupAggregationOperation(String from, String localField,
String foreignField, String as) {
this.operation = new BasicDBObject("$lookup", //
new BasicDBObject("from", from) //
.append("localField", localField) //
.append("foreignField", foreignField) //
.append("as", as));
}
#Override
public DBObject toDBObject(AggregationOperationContext context) {
return context.getMappedObject(operation);
}
}
The problem is that the slice command is not yet implemented as method in Aggregation (and isn't in 1.9.2), hence i need to call project("slice", -1, 1).as("$arrayItem") as suggested in DATAMONGO-1457. The slicing is simply not executed, arrayItem is null in the result.
I'm using Spring 1.3.5 with Spring MongoDB 1.8.4 and MongoDB 3.2.8.

How to add document in existing nested documents dynamically in mongodb

i have following structure
{
"name": "abc",
"lname": "xyz",
"data": {
"1": {
"info": {
"test": "test"
},
"info1": {
"test": "test"
}
}
}
}
now i want to add following object in 'data' object
"2": {
"info": {
"test": "test1"
},
"info1": {
"test": "test1"
}
}
how to do that in mongodb using mongodb java driver?
In MongoDB shell you can do it as below :
db.collection.update( {_id:id} , { $set: { "data.2":
{
"info": {"test": "test1" }, "info1": {"test": "test1"}
}
}});
In Java driver :
DBObject query = new BasicDBObject("_id", "123");
DBObject update = new BasicDBObject();
DBObject info = new BasicDBObject("test","test1");
update.put("$set", new BasicDBObject("data.2",
new BasicDBObject("info",info).append("info1",info));
collection.update(query, update);

Categories