Spring Data MongoDB Aggregation conversion? - java

I couldn't convert below mongodb aggregate operation to Spring Data AggregationOperation. I am using Spring Data MongoDB 1.3.2 version.
db.ads.aggregate( { $group :{
_id : "$adId",
req : { $sum : 1 },
imp: {$sum: { $cond: [ { $eq: [ "$imped", true ] } , 1, 0 ] } },
click: {$sum: { $cond: [ { $eq: [ "$clked", true ] } , 1, 0 ] } } ,
bid: {$sum: { $cond: [ { $eq: [ "$clked", true ] } , "$bid", 0 ] } } } });
I stopped here:
AggregationOperation group = Aggregation.group("adId").count().as("req").sum("imped").as("imp").;;
I would appreciate any help, thanks.

Currently there is no support to have $cmp/$eq/$ne to be used in group
or project aggregation. It would be a good to have feature. Also it
would be helpful to improve on some of the documentation/examples for
Criteria features.
please let's vote in here: https://jira.springsource.org/browse/DATAMONGO-784

Related

How to get data from mongodb with duplicated parameters?

I have try to create criteria that fetch from data base items.
Here is the code that fetches items from mongo db:
public List<Location> findByListOfId(List<String> locationsIds){
Query query = new Query();
query.addCriteria(Criteria.where("id").in(locationsIds));
return template.find(query, Location.class);
}
here is Location class defenition:
#Document("loaction")
#Data
public class Location {
#Id
private String id;
private long order;
private Date createdAt;
private Date updatedAt;
}
And here is the value of input(List locationsIds) in findByListOfId function:
List<String> locationsIds = {"5d4eee8047206b6d2df212bb","5d4eee8047206b6d2df212bb","5d4eee8047206b6d2df212bb"}
as you can see the input contains the same value three times.
The result that I get from findByListOfId function is a single item with id equal to 5d4eee8047206b6d2df212bb,
while I need to get the numbers of items with the same id as a number of times that exists with in variable(in my case I expect 3 fetched items with id = 5d4eee8047206b6d2df212bb ).
Any idea how this query can be created?
Not sure why you want to do it, but you can do it this way (in Mongo Query Language, you can then translate it in Java).
MongoDB Playground
db.collection.aggregate([
{
$match: {
key: {
$in: [
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb"
]
}
}
},
{
"$addFields": {
"itemsArray": [
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb"
]
}
},
{
"$unwind": "$itemsArray"
},
])
Using aggregation pipeline, you will add the array as a field using $addFields and then $unwind it (will give you x number of times).
I agree with others it's not something you want to do in production code, but I find the question interesting.
#Yahya's answer works with an assumption that the $match stage returns exactly 1 document.
The more generic pipeline to fetch exact number of documents regardless of how unique the key is and how many duplicates are in the query https://mongoplayground.net/p/546QnaFn4lV :
db.collection.aggregate([
{
$limit: 1
},
{
$project: {
_id: 1,
list: [
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb",
"6d4eee8047206b6d2df212bc",
"7d4eee8047206b6d2df212bd"
]
}
},
{
"$unwind": "$list"
},
{
"$lookup": {
"from": "collection",
"localField": "list",
"foreignField": "key",
"as": "match"
}
},
{
$project: {
match: {
$cond: [
{
$eq: [
"$match",
[]
]
},
[
{
_id: null,
"key": "$list"
}
],
"$match"
]
}
}
},
{
"$replaceWith": {
$first: "$match"
}
}
])
The first $project passes the list of requested ids to mongo.
The last $project stage returns "null" for requested ids that don't have a matching document.
Here is an aggregate query with required result:
Consider a collection with these documents:
{ _id: 1, a: 11 }
{ _id: 2, a: 22 }
{ _id: 3, a: 99 }
The query in mongo shell with input documents:
var INPUT_IDS = [ 1, 2, 1, 1 ]
db.collection.aggregate([
{
$match: {
_id: { $in: INPUT_IDS }
}
},
{
$group: {
_id: null,
docs: { $push: "$$ROOT" }
}
},
{
$project: {
docs: {
$map: {
input: INPUT_IDS,
as: "inid",
in: {
$let: {
vars: {
matched: {
$filter: {
input: "$docs", as: "doc", cond: { $eq: [ "$$inid", "$$doc._id" ] }
}
}
},
in: { $arrayElemAt: [ "$$matched", 0 ] }
}
}
}
}
}
},
{
$unwind: "$docs"
},
{
$replaceWith: "$docs"
}
])
The output:
{ "_id" : 1, "a" : 11 }
{ "_id" : 2, "a" : 22 }
{ "_id" : 1, "a" : 11 }
{ "_id" : 1, "a" : 11 }

How to get the count of element with non-empty-array-field when group in mongodb aggregate using Spring Data Mongo?

I have the following documents in one collection named as mail_test. Some of them have a tags field which is an array:
/* 1 */
{
"_id" : ObjectId("601a7c3a57c6eb4c1efb84ff"),
"email" : "aaaa#bbb.com",
"content" : "11111"
}
/* 2 */
{
"_id" : ObjectId("601a7c5057c6eb4c1efb8590"),
"email" : "aaaa#bbb.com",
"content" : "22222"
}
/* 3 */
{
"_id" : ObjectId("601a7c6d57c6eb4c1efb8675"),
"email" : "aaaa#bbb.com",
"content" : "33333",
"tags" : [
"x"
]
}
/* 4 */
{
"_id" : ObjectId("601a7c8157c6eb4c1efb86f4"),
"email" : "aaaa#bbb.com",
"content" : "4444",
"tags" : [
"yyy",
"zzz"
]
}
There are two documents with non-empty-tags, so I want the result to be 2.
I use the the following statement to aggregate and get the correct tag_count:
db.getCollection('mail_test').aggregate([{$group:{
"_id":null,
"all_count":{$sum:1},
"tag_count":{"$sum":{$cond: [ { $ne: ["$tags", undefined] }, 1, 0]}}
//if replace `undefined` with `null`, I got the tag_count as 4, that is not what I want
//I also have tried `$exists`, but it cannot be used here.
}}])
and the result is:
{
"_id" : null,
"all_count" : 4.0,
"tag_count" : 2.0
}
and I use spring data mongo in java to do this:
private void test(){
Aggregation agg = Aggregation.newAggregation(
Aggregation.match(new Criteria()),//some condition here
Aggregation.group(Fields.fields()).sum(ConditionalOperators.when(Criteria.where("tags").ne(null)).then(1).otherwise(0)).as("tag_count")
//I need an `undefined` instead of `null`,or is there are any other solution?
);
AggregationResults<MailTestGroupResult> results = mongoTemplate.aggregate(agg, MailTest.class, MailTestGroupResult.class);
List<MailTestGroupResult> mappedResults = results.getMappedResults();
int tag_count = mappedResults.get(0).getTag_count();
System.out.println(tag_count);//get 4,wrong
}
I need an undefined instead of null but I don't know how to do this,or is there are any other solution?
You can use Aggregation operators to check if the field tags exists or not with one of the following constructs in the $group stage of your query (to calculate the tag_count value):
"tag_count":{ "$sum": { $cond: [ { $gt: [ { $size: { $ifNull: ["$tags", [] ] }}, 0 ] }, 1, 0] }}
// - OR -
"tag_count":{ "$sum": { $cond: [ $eq: [ { $type: "$tags" }, "array" ] }, 1, 0] }
Both, return the same result (as you had posted).

How to write mongo cli query in mongo-template for $in aggregation

This is how my data looks like
{
"_id" : "2011250546437843117",
"name" : "Book",
"textbook" : [
"Maths",
"Science"
],
"language" : [
"English"
],
"isRead" : true,
"isAvailable" : true
}
I have to filter documents based on textbook,and based on that isRead field should be true or false.
my mongo query is
db.user.aggregate([
{
$match: {
"isAvailable": true
}
},
{
$project: {
"textbook": 1,
"name": 1,
"isread": {
$in: [
"Maths",
"$textbook"
]
}
}
}
]);
I have tried to write this using mongo-template
Aggregation aggregation = newAggregation(match(Criteria.where("isAvailable").is(true)),
project("textbook","name"));
I dont understand how to write the $in operator in project stage.
Thankyou in advance.

Spring MongoDB Aggregation Group - Can't get the group query right

I have a document in a MongoDB, which looks like follows.
{
"_id" : ObjectId("5ceb812b3ec6d22cb94c82ca"),
"key" : "KEYCODE001",
"values" : [
{
"classId" : "CLASS_01",
"objects" : [
{
"code" : "DD0001"
},
{
"code" : "DD0010"
}
]
},
{
"classId" : "CLASS_02",
"objects" : [
{
"code" : "AD0001"
}
]
}
]
}
I am interested in getting a result like follows.
{
"classId" : "CLASS_01",
"objects" : [
{
"code" : "DD0001"
},
{
"code" : "DD0010"
}
]
}
To get this, I came up with an aggregation pipeline in Robo 3T, which looks like follows. And it's working as expected.
[
{
$match:{
'key':'KEYCODE001'
}
},
{
"$unwind":{
"path": "$values",
"preserveNullAndEmptyArrays": true
}
},
{
"$unwind":{
"path": "$values.objects",
"preserveNullAndEmptyArrays": true
}
},
{
$match:{
'values.classId':'CLASS_01'
}
},
{
$project:{
'object':'$values.objects',
'classId':'$values.classId'
}
},
{
$group:{
'_id':'$classId',
'objects':{
$push:'$object'
}
}
},
{
$project:{
'_id':0,
'classId':'$_id',
'objects':'$$objects'
}
}
]
Now, when I try to do the same in a SpringBoot application, I can't get it running. I ended up having the error java.lang.IllegalArgumentException: Invalid reference '$complication'!. Following is what I have done in Java so far.
final Aggregation aggregation = newAggregation(
match(Criteria.where("key").is("KEYCODE001")),
unwind("$values", true),
unwind("$values.objects", true),
match(Criteria.where("classId").is("CLASS_01")),
project().and("$values.classId").as("classId").and("$values.objects").as("object"),
group("classId", "objects").push("$object").as("objects").first("$classId").as("_id"),
project().and("$_id").as("classId").and("$objects").as("objects")
);
What am I doing wrong? Upon research, I found that multiple fields in group does not work or something like that (please refer to this question). So, is what I am currently doing even possible in Spring Boot?
After hours of debugging + trial and error, found the following solution to be working.
final Aggregation aggregation = newAggregation(
match(Criteria.where("key").is("KEYCODE001")),
unwind("values", true),
unwind("values.objects", true),
match(Criteria.where("values.classId").is("CLASS_01")),
project().and("values.classId").as("classId").and("values.objects").as("object"),
group(Fields.from(Fields.field("_id", "classId"))).push("object").as("objects"),
project().and("_id").as("classId").and("objects").as("objects")
);
It all boils down to group(Fields.from(Fields.field("_id", "classId"))).push("object").as("objects") that which introduces a org.springframework.data.mongodb.core.aggregation.Fields object that wraps a list of org.springframework.data.mongodb.core.aggregation.Field objects. Within Field, the name of the field and the target could be encapsulated. This resulted in the following pipeline which is a match for the expected.
[
{
"$match" :{
"key" : "KEYCODE001"
}
},
{
"$unwind" :{
"path" : "$values", "preserveNullAndEmptyArrays" : true
}
},
{
"$unwind" :{
"path" : "$values.objects", "preserveNullAndEmptyArrays" : true
}
},
{
"$match" :{
"values.classId" : "CLASS_01"
}
},
{
"$project" :{
"classId" : "$values.classId", "object" : "$values.objects"
}
},
{
"$group" :{
"_id" : "$classId",
"objects" :{
"$push" : "$object"
}
}
},
{
"$project" :{
"classId" : "$_id", "objects" : 1
}
}
]
Additionally, figured that there is no need to using $ sign anywhere and everywhere.

MongoDB how translate switch/case with expression to java driver syntax?

I have a projection field computed from some conditions in the current document. The native mongo query works fine. But I cant implement the query in java driver 3.4. Only java driver 3.4 syntax is relevant.
The projection code for field result from switch is:
"SITUACAO": {
"$switch" : {
"branches": [
{ case: {"$eq": ["$ID_STATUSMATRICULA", 0]},
then: {
"$switch" : {
"branches": [
{ case: {"$and": [{"$eq": ["$NR_ANDAMENTO", 0 ] },
{"$eq": ["$ID_STATUSMATRICULA", 0]} ] }, then: "NAOINICIADO" },
{ case: {"$and": [{"$gt": ["$NR_ANDAMENTO", 0]},
{"$lte": ["$NR_ANDAMENTO", 100]},
{"$eq": ["$ID_STATUSMATRICULA", 0]} ] }, then: "EMANDAMENTO" }
],
"default": "--matriculado--"
}
}
},
{ case: {"$eq": ["$ID_STATUSMATRICULA", 1]},
then: {
"$switch" : {
"branches": [
{ case: {"$and": [ {"$eq": ["$ID_STATUSMATRICULA", 1]},
{"$in": ["$ID_STATUSAPROVEITAMENTO", [1] ]} ] }, then: "APROVADO" },
{ case: {"$and": [ {"$eq": ["$ID_STATUSMATRICULA", 1]},
{"$in": ["$ID_STATUSAPROVEITAMENTO", [2] ]} ] }, then: "REPROVADO" },
{ case: {"$and": [{"$eq": ["$ID_STATUSMATRICULA", 1]},
{"$in": ["$ID_STATUSAPROVEITAMENTO", [0] ]} ] }, then: "PENDENTE" },
{ case: {"$and": [ {"$eq": ["$ID_STATUSMATRICULA", 1]},
{"$in": ["$ID_STATUSAPROVEITAMENTO", [1,2] ]} ] }, then: "CONCLUIDO" }
],
"default": "--concluido--"
}
}
}
],
"default": "--indefinida--"
}
}
The part around $and inside case statments I can draw like this:
List<Document> docs = new ArrayList<>();
docs.add( new Document("$eq", asList("$NR_ANDAMENTO", 0)) );
docs.add( new Document("$eq", asList("$ID_STATUSMATRICULA", 1)) );
Document doc = new Document("$and", docs);
but, the structure $switch / branches[] / case ... is dificult to find the way to write.
Anybody have an example like this or some idea for write this ?
Thanks

Categories