I need help with Spring Data Java API. I am writing aggregation pipeline for the report. The last stage is "$project" where I set the data for output. In Mongodb shell the pipeline works just fine but I can't find the way to code "generatedDate: new Date()" with Spring Data SDK for MongoDB.
Here is how the "$project" should look like:
{$project: {
coach: {
firstName: '$coach.firstName',
lastName: '$coach.lastName',
employeeId: '$coach._id'
},
patient: {
firstName: '$patientContact.patient.firstName',
lastName: '$patientContact.patient.lastName',
contactId: '$patientContact._id'
},
customerName: '$patientContact.organization.name',
coachingSummary: 1,
formCreatedDate: 1,
generatedDate: new Date() //<<--- This is what I want
}}
I want the server to generate new Date instance and return it in the response.
Here is my Java code for this aggregation stage:
ProjectionOperation finalProject = project("coachingSummary", "formCreatedDate")
.and("$patientContact.organization.name").as("customerName")
.and("coach")
.nested(Fields.from(
Fields.field("firstName", "coach.firstName"),
Fields.field("lastName", "coach.lastName"),
Fields.field("employeeId", "coach._id")
))
.and("patient")
.nested(Fields.from(
Fields.field("firstName", "patientContact.patient.firstName"),
Fields.field("lastName", "patientContact.patient.lastName"),
Fields.field("contactId", "patientContact._id")
))
.and("<SOMETHING_GOES_HERE>").as("generatedDate");//<<-- How to code it?
Related
I am trying to convert a native mongo query to jpa.
The basis is that I have to subtract two dates that are in string format, that is, first I have to convert and then use datediff, this is how I get the result I want from my compass client:
$project: {
waiting: {
$dateDiff: {
startDate: {
$toDate: "$date_start"
},
endDate: {
$toDate: "$date_end"
},
unit: "second"
}
}
_id: 0
}
In the documentation there is a reference to the use of datediff but I don't see any example and I'm somewhat new to this technology
https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo.aggregation
(Date Aggregation Operators)
this has been the closest I have come, giving a failed result because the dates are not in date format
ProjectionOperation projectStage = Aggregation.project("test").andExpression("date_start - date_end").as("test");
error during aggregation :: caused by :: can't $subtract string from string"
If you can help me, I have an update in mongo with $cond , this update is only done if the field is empty, otherwise it updates the field with another value. Example in mongo db
I want to update the field camp1
if camp1 no exits = values
if camp1 exits = value2
db.getCollection('prueba').update(
{"cdAccount": "ES3100810348150001326934"},
[{$set:{camp1 :{"$cond": [{"$not": ["$camp1"]}, "values", "value2"]}}}]);
Result:
{
"_id" : ObjectId("62dd08c3f9869303b79b323b"),
"cdAccount" : "ES3100810348150001326934",
"camp1" : "value2"
}
Now I do the same in scala with this code
def appendIfNotNull(key: String,value :Object) = {
var eq2Array = new util.ArrayList[Object]()
eq2Array.add("$"+key)
val eq2Op = new Document("$not", eq2Array)
var condList = new util.ArrayList[Object]()
condList.add(eq2Op)
condList.add(value.asInstanceOf[AnyRef])
//condList.add("$"+key)
condList.add("value2")
val availDoc =
new Document("$cond",
new Document("$cond", condList)).toBsonDocument(classOf[BsonDocument],getCodecRegistry).get("$cond")
println("availDoc : " + availDoc)
documentGrab.append(key,availDoc)
}
val finalVar = appendIfNotNull("camp1","values")
println("finalVar : " + finalVar)
availDoc : {"$cond": [{"$not": ["$camp1"]}, "values", "value2"]}
finalVar : Document{{camp1={"$cond": [{"$not": ["$camp1"]}, "values", "value2"]}}}
val updateDocument = new Document("$set" , finalVar )
println("updateDocument : " + updateDocument)
collectionA.updateMany(Filters.eq("cdAccount", "ES3100810348150001326934"),updateDocument)
The only difference I see is that in mongodb the "[" is added at the beginning of the $set and it does it well
MongoDB
[ {$set:{camp1 :{"$cond": [{"$not": ["$camp1"]}, "values", "value2"]}}} ] --> Ok Update
Scale
{$set:{camp1 :{"$cond": [{"$not": ["$camp1"]}, "values", "value2"]}}} --> Ok in scala , but I get the result II
I am using mongodb 5.0.9
Now in mongodb I execute the statement made in scala
db.getCollection('prueba').update(
{"cdAccount": "ES3100810348150001326934"},
{$set :{camp1 :{"$cond": [{"$not": ["$camp1"]}, "values", "value2"]}}});
When I run it in scala the same thing happens
Result II :
{
"cdAccount" : "ES3100810348150001326934",
"camp1" : {
"$cond" : [
{
"$not" : [
"$camp1"
]
},
"values",
"value2"
]
}
}
Can someone tell me how to fix it?
Thank you so much
You see the very important difference when priting the queries.
$cond is an aggregation pipeline operator. It is processed only when aggregation pipeline is used to update the data. When a simple (non-pipelined) update is used, the operator has no special meaning and this is exactly what you see in the output.
You indicate "pipeline update" by passing an array instead of simple object as update description in javascript API (and mongo console). In Scala/Java you have to use one of the updateMany overloads that takes update description as List, not Bson. I.e. you need something like
collectionA.updateMany(
Filters.eq("cdAccount", "ES3100810348150001326934"),
Collections.singletonList(updateDocument)
)
Executing the following aggregation pipeline:
public void getMostLikedItems () {
UnwindOperation unwind = Aggregation.unwind("favoriteItems");
GroupOperation group = Aggregation.group("favoriteItems").count().as("likes");
SortOperation sort = Aggregation.sort(Sort.Direction.DESC, "likes");
Aggregation aggregation = newAggregation(unwind, group, sort);
DBObject result = mongoTemplate.aggregate(aggregation, "users", LikedItem.class).getRawResults();
}
throws the following exception:
com.mongodb.MongoCommandException: Command failed with error 9: 'The 'cursor' option is required, except for aggregate with the explain argument' on server localhost:27017. The full response is { "ok" : 0.0, "errmsg" : "The 'cursor' option is required, except for aggregate with the explain argument", "code" : 9, "codeName" : "FailedToParse" }
I don't understand what is meant by cursor option here. Where should this option be configured?
EDIT Here is a sample user document
{
"_id": "5a6df13552f42a34dcca9aa6",
"username": "user1",
"password": "$2a$10$p0OXq5PPa41j1e4iPcGZHuWjoKJ983sieS/ovFI.cVX5Whwj21WYi",
"favoriteItems": [
{
"_id": "5a0c6b2dfd3eb67969316d6d",
"name": "item1",
"city": "Rabat"
},
{
"_id": "5a0c680afd3eb67969316d0b",
"name": "item2",
"city": "Rabat"
}
]
}
From the docs.
MongoDB 3.4 deprecates the use of aggregate command without the cursor
option, unless the pipeline includes the explain option. When
returning aggregation results inline using the aggregate command,
specify the cursor option using the default batch size cursor: {} or
specify the batch size in the cursor option cursor: { batchSize:
}.
You can pass batchSize with AggregationOptions in Spring Mongo 2.x version
Aggregation aggregation = newAggregation(unwind, group).withOptions(newAggregationOptions().cursorBatchSize(100).build());
With default batch size
Aggregation aggregation = newAggregation(unwind, group).withOptions(newAggregationOptions().cursor(new Document()).build());
'The 'cursor' option is required, except for aggregate with the explain argument'
This type of error raised in spring data when you are using incompatible versions of MongoDB and Spring-data-mongo.
Though you can get rawResults with explain, cursor arguments.
Aggregation aggregation = Aggregation.newAggregation(group).withOptions( new AggregationOptions(allowDiskUse, explain, cursor));
//try with .withOptions( new AggregationOptions(true,false,new Document()));
Passing by commented Arguments you will get result in rawResult but it will not be mapped in given outType.class.
To get mapped result you have to download right dependency of spring-data version according to your MongoDb version.
EDIT
I have used Spring version 5.0.3 and Spring-data-mongoDB version 2.0.3
It is working Fine.
You can provide outputmode as cursor as providing a cursor is mandatory
List<DBObject> list = new ArrayList<DBObject>();
list.add(unwind.toDBObject(Aggregation.DEFAULT_CONTEXT));
list.add(group.toDBObject(Aggregation.DEFAULT_CONTEXT));
list.add(sort.toDBObject(Aggregation.DEFAULT_CONTEXT));
DBCollection col = mongoTemplate.getCollection("users");
Cursor cursor = col.aggregate(list, AggregationOptions.builder().allowDiskUse(true).outputMode(OutputMode.CURSOR).build());
List<AggregationResultVO> result = new ArrayList<AggregationResultVO>();
while(cursor.hasNext()) {
DBObject object = cursor.next();
result.add(new AggregationResultVO(object.get("aggregationResultId").toString()));
}
How to implement the following Mongo DB query using Spring Data?
db.getCollection('docs').aggregate([
{
$project: {
values: [ "$a", "$b" ]
}
}
])
AggregationOperation projectStage = Aggregation.project()
.andArrayOf("$a","$b").as("values");
I'm trying to convert this javascript code into java code to be used in Spring Data:
proj3={"$project": {
"comms" : 1,
"same" : { "$eq" : ["$comms.i" , "$max"]},
"max" : 1,
"_id" : 1
}
};
I cannot seem to figure it out.
I have tried this:
BasicDBObject o3 = new BasicDBObject();
o3.append("$eq", "[\"$comms.i\",\"$max\"]");
Aggregation aggCount2 = newAggregation(project("comms", "max", "_id").andExpression("same", o3));
logger.info(aggCount2.toString());
This is what is logged:
{ "$project" : { "comms" : 1 , "max" : 1 , "_id" : 1}}
I also read this thread: Spring Data MongoDB - $eq within $project support but the poster seemed to have given up and used the executeCommand option instead which is not the route I would like to go.
How can I get this code to work in java Spring Data Mongodb?
This is how I solved it, it might not be the most efficient way but it seem to work without too much code rewrite.
First, I looked at the answer in this thread:
Aggregation Project Group By extracted day, month and year with Spring Data
Blakes Seven proposed that I use a special custom aggregationOperation class so that the aggregation code will take BasicDBObjects:
public class CustomGroupOperation implements AggregationOperation {
private DBObject operation;
public CustomGroupOperation (DBObject operation) {
this.operation = operation;
}
#Override
public DBObject toDBObject(AggregationOperationContext context) {
return context.getMappedObject(operation);
}
}
Next, you just format the project code you want into the BasicDBObject:
BasicDBList basicDBList = new BasicDBList();
basicDBList.add("$comms.i");
basicDBList.add("$max");
Aggregation aggCount2 = newAggregation(
match(),
project(),
group(),
new CustomGroupOperation(new BasicDBObject("$project",
new BasicDBObject("comms", 1)
.append("max", 1)
.append("_id", 1)
.append("same", new BasicDBObject("$eq", basicDBList)))),
match(),
project(),
group(),
sort());
Print it out in the logger and you will see that the format for the javascript code is now correct.
{ "$project" : { "comms" : 1 , "max" : 1 , "_id" : 1 , "same" : { "$eq" : [ "$comms.i" , "$max"]}}}