skip to Main Content

I am trying to implement a grouping with a TopN aggregation operator using spring-data-mongo and I am lost on how to do it.

I know what I want from the POV of MongoDB. It’s something like this:

[ { 
    $match: { 
      field000: { $regex: ".*MATCHTHIS.*" }, 
      created: { $lte: new Date("2030-05-25T00:00:00.000+00:00" ) } 
    }, 
  }, 
  { 
     $group: { 
       _id: "$field001", 
       field001s: { 
         $topN: { 
           output: ["$field002", "$created"], 
           sortBy: { created: -1, }, 
           n: 1, 
         }
       }
     }
    }]

Meaning … for the set of documents already filtered by the $match clause; group by field001, order each bucket by created desc, and pick the top (1). So the most recently created documents for each group category.

I find problems to translate this into spring-data-mongo

2

Answers


  1. Chosen as BEST ANSWER

    After a lot of research, I realized this question is probably easily solvable on spring-data-mongodb most recent versions since "topN" aggregation operator is implemented. However in my case upgrading the stack to support that version is not an option.

    On the other hand, if you are using repositories then the @Charchit Kapoor solution is probably the best one.

    If you want to stick with mongo template it can be done this way:

        AggregateIterable<Document> result = mongoOps.getCollection(COLLECTION_NAME).aggregate(Arrays.asList(new Document("$match",
            new Document("field000",
              new Document("$regex", ".*REGEXP_FIELD_000.*"))
              .append("created",
                new Document("$lte",
                  new java.util.Date(1685318400000L)))),
          new Document("$group",
            new Document("_id", "$field001")
              .append("Field0001s",
                new Document("$topN",
                  new Document("output", Arrays.asList("$field002", "$created"))
                    .append("sortBy",
                      new Document("created", -1L))
                    .append("n", 1L))))));
    

    You can split this into different methods for better readability.


  2. Using MongoRepository, you can specify the pipeline using, @Aggregate annotation. Something like this:

    @Aggregation(pipeline = {"{ $match: { field000: { $regex: '?0' }, created: { $lte: '?1' } },}, { $group: { _id: '$field001', field001s: { $topN: { output: ['$field002', '$created'], sortBy: { created: -1, }, n: 1}}}}"})
    Object filterAndGroup(String regex, ZonedDateTime created);
    

    Note that I have parameterized the search regex and date value. Please update it accordingly, along with the return type of the function. Using MongoTemplate, you can try something along these lines.

    MatchOperation matchStage = Aggregation.match(
       new Criteria("field000").regex(".*MATCHTHIS.*")
         .and(new Criteria("created").lte(YOUR_JAVA_DATE_OBJECT))
    );
    ProjectionOperation projectStage = Aggregation.project("field002", "created", "field001");
    SortOperation sortByCreatedDesc = sort(Sort.by(Direction.DESC, "created"));
    GroupOperation groupStage = Aggregation.group("field001").first("$$ROOT").as("field001s");
    
    Aggregation aggregation = newAggregation(
      matchStage, projectStage, sortByCreatedDesc, groupStage);
    AggregationResults<XYZModel> result = mongoTemplate.aggregate(
      aggregation, "collectionName", XYZModel.class);
    

    Note that, I have added two new stages for Projection and Sorting, as $topN is not yet supported by Spring Data MongoDB, so I am projecting the necessary fields, then sorting by created, and then grouping the document and picking the first one in each group.

    Note: The answer is not tested by me, so you will have to try it out and adjust it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search