0

I'm trying to implement in Spring Data using MongoTemplate the following working mongoDb query:

db.answers.aggregate([
        { "$match" : { "object_id" : "1" } },
        { "$project": { 'answer_list': 1, 'profile': { $filter : { input: '$answer_list', as: 'answer', cond: { $eq: [ '$$answer.question', 2 ] } } } } },
        { "$unwind" : "$profile"},
        { "$unwind" : "$answer_list"},
        { "$group" : { "_id" : { "question" : "$answer_list.question", "answer" : "$answer_list.answer", "criteria" : "$profile.answer"}, "count" : { "$sum" : 1 } } },
        { "$sort" : { "_id.question" : 1, "_id.answer" : 1 } }
]);

The collection has this structure:

{
"_id" : ObjectId("..."),
"object_id" : ObjectId("..."),
"answer_list" : [ 
    {
        "question" : NumberLong(0),
        "answer" : NumberLong(0)
    }, 
    {
        "question" : NumberLong(1),
        "answer" : NumberLong(2)
    }, 
    {
        "question" : NumberLong(2),
        "answer" : NumberLong(2)
    }
]}

What I'm trying to do here is a report on a simple survey submission data. The question is "How did the users that answered 0 to the first question answer to the second question?" I spent all day searching the SpringData Mongo Db docs but I found nothing. Can anyone help?

TIA

3
  • I've created DATAMONGO-1491 to add support for $filter. Commented Sep 13, 2016 at 7:03
  • Thanks Christoph. I hope to find a workaround until it's implemented Commented Sep 13, 2016 at 8:18
  • As of Spring Boot 3 (maybe also Spring Boot 2.x) the $filter is supported. In the AggregationTest class in the spring-data-mongodb repository you'll find some examples. Commented Jan 25, 2023 at 11:34

2 Answers 2

5

You can workaround this issue by providing your own AggregationExpression.

ProjectionOperation agg = Aggregation.project() //

      .and(new AggregationExpression() {

        @Override
        public DBObject toDbObject(AggregationOperationContext context) {

          DBObject filterExpression = new BasicDBObject();
          filterExpression.put("input", "$answer_list");
          filterExpression.put("as", "answer");
          filterExpression.put("cond", new BasicDBObject("$eq2", Arrays.<Object> asList("$$answer.question", 2)));

          return new BasicDBObject("$filter", filterExpression);
        }
      }).as("profile");
Sign up to request clarification or add additional context in comments.

15 Comments

That looks exactly what I need. The only problem is that interface AggregationExpression is not public, if I'm not missing something...
I've created jira.spring.io/browse/DATAMONGO-1492 suggesting that the interface should be public. I'm still searching for a way to implement your suggestion...
doh... my bad - that one should be public - thanks for filing the issue! Since AggregationExpression is package private you could add the package org.springframework.data.mongodb.core.aggregation to your project and create a public class/interface FilterExpression implementing/extending AggregationExpression within that package. Then just use that one from there.
I'm getting closer... this is my implementation: public class FilterExpression implements AggregationExpression { // private fields and constructor omitted for brevity @Override public DBObject toDbObject( AggregationOperationContext context ) { DBObject filterExpression = new BasicDBObject(); filterExpression.put("input", input); filterExpression.put("as", as); filterExpression.put("cond", cond); return new BasicDBObject("$filter", filterExpression); } }
The expression seems correct (invoking the toDBObject() and a toJson() produces: "{ "$filter" : { "input" : "$answer_list", "as" : "answer", "cond" : { "$eq2" : ["$$answer.question", 2] } } }") but I'm getting thi exception: org.springframework.data.mapping.PropertyReferenceException: No property answer found for type Opinion! at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:77) ...
|
4

There is one more alternative using Aggregation

import static org.springframework.data.mongodb.core.aggregation.ComparisonOperators.valueOf;

    Aggregation aggregation = newAggregation(
                               project()
                               .and(filter("answer_list")
                               .as("answer")
                           .by(valueOf("answer.question").equalToValue(2)))
                               .as("profile"));
    AggregationResults<OutputType> profile = mongoTemplate.aggregate(aggregation, InputType.class, OutputType.class);

I may not be able to answer your question correctly but I just wanted to give another approach for aggregation as there are lesser number of examples using Aggregation.

In project() you can specify keys you want in your response, as its a varargs method

2 Comments

I like this answer better. However, please include static imports you are using in your answer. For anyone else, the valueOf() operator comes from here: import static org.springframework.data.mongodb.core.aggregation.ComparisonOperators.valueOf;
In the AggregationTest class in the spring-data-mongodb repository you'll find more examples. E.g. for for filtering within a project see here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.