Search code examples
mongodbaggregation-frameworkmongo-java

mongocollection filter on array elements


I have a document with the following structure

    {
        "id" : "abcdefg",
        "sub" : {"field1" : "value1", "field3" : "value3"},
        "profile" : {
            "array" : [
                {"score" : 1},
                {"score" : 2},
                {"score" : 3}
            ]
        }
    }

I want to filter on profile.score.score, I use the following

    Document idQuery = new Document("id", new Document("$in", ids)); 
                 // ids is an array of id
    Document idMatch = new Document("$match", idQuery);
    Document projectArray = new Document("$project", new Document("id",1).append("profile.array", 1).append("sub", 1));
    Document unwindArray = new Document("$unwind", "$profile.array");
    Document filterQuery = new Document("profile.array.score", new Document("$gte", 2));
    Document filterMatch = new Document("$match", filterQuery);
    Document groupResult = new Document("$group", new Document("_id", "$id").append("array", new Document("$push", "$profile.array")).append("sub", new Document("$push", "$sub")));
    List<Document> pipeLineList = new ArrayList<Document>();
    pipeLineList.add(idMatch);
    pipeLineList.add(projectArray);
    pipeLineList.add(unwindArray);
    pipeLineList.add(filterMatch);
    pipeLineList.add(groupResult);
    AggregateIterable<Document> result = companyProfiles.aggregate(pipeLineList);

By doing above, I have the result with

{
    "id" : "abcdefg",
    "sub" : [
              {"field1" : "value1", "field3" : "value3"},
              {"field1" : "value1", "field3" : "value3"}
            ]
    "profile" : {
        "array" : [
            {"score" : 2},
            {"score" : 3}
        ]
    }
}

The sub field was inserted twice as elements in an array, which is not what I want. What should I do to have the correct result

{
    "id" : "abcdefg",
    "sub" : {"field1" : "value1", "field3" : "value3"},
    "profile" : {
        "array" : [
            {"score" : 2},
            {"score" : 3}
        ]
    }
}

Solution

  • Change your $group stage to use $first instead of $push to avoid duplicates.

    Document groupResult = new Document("$group", new Document("_id", "$id").append("array", new Document("$push", "$profile.array")).append("sub", new Document("$first", "$sub")));
    

    Alternatively you can refactor your code to use $filter which replaces $unwind + $match + $group.

    Document idQuery = new Document("id", new Document("$in", ids));
    Document idMatch = new Document("$match", idQuery);
    Document filterQuery = new Document("$gte", Arrays.asList("$$profilearray.score", 2));
    Document filterArray = new Document("$filter", new Document("input", "$profile.array").append("as", "profilearray").append("cond", filterQuery));
    Document projectArray = new Document("$project", new Document("id",1).append("profile.array", filterArray).append("sub", 1));
    List<Document> pipeLineList = new ArrayList<Document>();
    pipeLineList.add(idMatch);
    pipeLineList.add(projectArray);