我该如何在Spring中编写Mongo聚合缩减查询?

4

Mongo中的数据:

点击此处查看图片

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、过滤掉评论和评论2中评分大于5的。 2、计算评论数组中所有评分的总和。

我在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")
    );

当我执行类似于 "up" 的操作时,它会抛出异常:

java.lang.IllegalArgumentException: Invalid reference '$$value'!


似乎是两个项目阶段导致了这种行为。如果您只需要计数作为最终输出,就像您shell查询中显示的那样,我们可以很容易地解决此问题。 - s7vr
我现在该怎么做? - show.so
4个回答

2

您可以尝试在$reduce操作中包含$filter来进行聚合。

类似于下面的示例:

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")
);

我尝试像这样:AggregationExpression reduce = ArithmeticOperators.Add.valueOf("$$value").add("$$this.socre"); Aggregation aggregation = Aggregation.newAggregation( Aggregation.project() .andInclude("content") .and(ArrayOperators.Reduce.arrayOf( ArrayOperators.Filter.filter("comments").as("item").by(ComparisonOperators.Gt.valueOf("item.score").greaterThanValue(2))). withInitialValue(0).reduce(reduce)).as("commentsTotal") ); - show.so
异常: org.springframework.dao.InvalidDataAccessApiUsageException: 命令执行失败:错误[使用未定义的变量:value],命令 = {"aggregate":"test2","pipeline":[{"$project":{"content":1,"commentsTotal":{"$reduce":{"input":{"$add":["$ $ value","$ $ this.socre"]},"initialValue":0,"in":{"$add":["$ $ value","$ $ this.socre"]}}}}}]}; 嵌套异常是com.mongodb.CommandFailureException:{"serverUsed":"git.ivymei.com:27017","ok":0.0,"errmsg":"使用未定义的变量:value","code":17276,"codeName":"Location17276"}。 - show.so
抱歉,看起来链式聚合运算符没有按预期工作。我已经用另一种解决方案代替了它。 - s7vr

2

这是一个老问题,但如果有人像我一样来到这里,这就是我如何解决它的方法。

在Spring中,您不能直接访问"$$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());

希望这能帮到你!

1

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

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

1

我必须解决下一个任务,但是没有找到任何解决方案。所以我希望我的答案能帮助某些人。

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

用户结构

角色结构

首先,我查找角色到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)));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接