如何在Spring中编写mongo聚合减少查询?

时间:2017-07-10 10:00:21

标签: java spring mongodb

mongo中的数据:

enter image description here

db.test2.aggregate([
    {
        "$project" : {
        "contents" : 1,
        "comments" : {
            "$filter" : {
                "input" : "$comments", 
                "as" : "item", 
                "cond" : {"$gt" : ['$$item.score', 2]}

                },
            },
         "comments2" : {
            "$filter" : {
                "input" : "$comments2", 
                "as" : "item", 
                "cond" : {"$gt" : ["$$item.score", 5]}
            }
            }
        }
     },
     {
     "$project" : {
            "content" : 1,
            "commentsTotal" : {
                "$reduce" : {
                    "input" : "$comments",
                    "initialValue" : 0,
                    "in" : {"$add" : ["$$value", "$$this.score"]}
                }
                },
            "comments2Total" : {
                "$reduce" : {
                "input" : "$comments2",
                "initialValue" : 0,
                    "in" : {"$add" : ["$$value", "$$this.score"]}
                }
            }
        }
      },
      {$skip : 0},
      {$limit: 3}

  ]);
  <!-- language: lang-json-->

所以你可以看到,这会做到以下几点: 1,过滤得分为gt 5的评论和评论2。 2,计算注释数组中的socre总数。

我在Spring中编写聚合查询,如下所示:

AggregationExpression reduce = ArithmeticOperators.Add.valueOf("$$value").add("$$this.socre");
    Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.project().andExclude("_id")
                    .andInclude("content")
                    .and("comments").filter("item", ComparisonOperators.Gt.valueOf("item.score").greaterThanValue(3)).as("comments")
                    .and("comments2").filter("item", ComparisonOperators.Gt.valueOf("item.score").greaterThanValue(3)).as("comments2"),
            Aggregation.project("comments", "comments2")
                    .and(ArrayOperators.Reduce.arrayOf("comments").withInitialValue("0").reduce(reduce)).as("commentsTotal")
    );

当我跑起来时,它会抛出异常:

  

java.lang.IllegalArgumentException:无效的引用'$$ value'!

4 个答案:

答案 0 :(得分:2)

您可以通过在$filter操作中包装$reduce来尝试以下聚合。

如下所示

AggregationExpression reduce1 = new AggregationExpression() {
        @Override
        public DBObject toDbObject(AggregationOperationContext aggregationOperationContext) {
         DBObject filter = new BasicDBObject("$filter", new BasicDBObject("input", "$comments").append("as", "item").append("cond",
                    new BasicDBObject("$gt", Arrays.<Object>asList("$$item.score", 2))));
         DBObject reduce = new BasicDBObject("input", filter).append("initialValue", 0).append("in", new BasicDBObject("$add", Arrays.asList("$$value", "$$this.socre")));
         return new BasicDBObject("$reduce", reduce);
     }
 };

Aggregation aggregation = newAggregation(
     Aggregation.project().andExclude("_id")
       .andInclude("content")
       .and(reduce1).as("commentsTotal")
);

答案 1 :(得分:1)

这是一个古老的问题,但是如果有人像我一样出现在这里,这就是我能够解决的方法。

在春季,您不能像这样直接访问“ $$ this ”和“ $$ value ”变量。

AggregationExpression reduce = ArithmeticOperators.Add.valueOf("$$value").add("$$this.socre");

为此,我们必须使用reduce变量枚举,如下所示:

AggregationExpression reduce = ArithmeticOperators.Add.valueOf(ArrayOperators.Reduce.Variable.VALUE.getTarget()).add(ArrayOperators.Reduce.Variable.THIS.referringTo("score").getTarget());

希望这会有所帮助!

答案 2 :(得分:0)

我不得不解决下一个任务,没有找到任何解决方案。因此,我希望我的回答对您有所帮助。

具有角色的用户(用户具有权限列表+角色列表,每个角色都有自己的权限列表,需要查找完整的权限列表):

user structure

role structure

首先,我查找角色到roleDto(例如),然后将角色的权限收集到1个列表中:

ArrayOperators.Reduce reduce = ArrayOperators.Reduce.arrayOf("$roleDto.rights")
        .withInitialValue(new ArrayList<>())
        .reduce(ArrayOperators.ConcatArrays.arrayOf("$$value").concat("$$this"));

由于减少,我从角色那里收集了这1项权利列表。 之后,我做:

SetOperators.SetUnion.arrayAsSet(reduce).union("$rights")

使用先前的结果。结果类型为AggregationExpression,因为AbstractAggregationExpression实现了AggregationExpression。

所以,最后我得到了这样的信息(对不起,代码混乱):

private static AggregationExpression getAllRightsForUser() {
    // concat rights from list of roles (each role have list of rights) - list of list to list
    ArrayOperators.Reduce reduce = ArrayOperators.Reduce.arrayOf("$roleDto.rights")
        .withInitialValue(new ArrayList<>())
        .reduce(ArrayOperators.ConcatArrays.arrayOf("$$value").concat("$$this"));
    // union result with user.rights
    return SetOperators.SetUnion.arrayAsSet(reduce).union("$rights");
}

该操作的结果可以最终在此处使用;):

public static AggregationOperation addFieldOperation(AggregationExpression aggregationExpression, String fieldName) {
    return aoc -> new Document("$addFields", new Document(fieldName, aggregationExpression.toDocument(aoc)));
}

答案 3 :(得分:0)

我遇到了同样的问题,解决方案之一是创建一个自定义的Reduce函数,这是Union示例:

public class SetUnionReduceExpression implements AggregationExpression {
    @Override
    public Document toDocument(AggregationOperationContext context) {
        return new Document("$setUnion", ImmutableList.of("$$value", "$$this"));
    }
}