我该如何在Spring中编写这个Mongo聚合查询?

3

我在MongoDB中有一个聚合查询,当我直接在shell中运行它时可以正常工作。以下是shell查询:

db.MyCollection.aggregate([
    {$match: {_id: {$in: ['A', 'B', 'C']}}},
    {$project: {"versions": "$nested.field.version"}},
    {$unwind: "$versions"},
    {$group: {_id: "$_id", "maxVersion": {$max: "$versions"}}}
])

正如您所看到的,它会执行以下操作:
  1. 仅匹配具有指定ID的某些文档
  2. 将嵌套字段投影到基本级别字段(有效地从管道中过滤掉所有其他字段,但仍保留ID)
  3. 展开我们在管道中投影为单个文档的$versions字段的数组元素
  4. 查找每个ID的$versions的最大值
像我说的那样,上面的查询已经起作用了。我的问题是如何将其转换为Spring MongoDB语法。这是我的第一次尝试,但不起作用:
Aggregation aggregation = newAggregation(
    match(Criteria.where("_id").in(listOfIds))
    ,project().and("versions").nested(bind("versions", "nested.field.version"))
    ,unwind("versions")
    ,group("_id").max("versions").as("maxVersion")
);

当我尝试以调试模式运行代码时,我发现在newAggregation上实际上会出现IllegalArgumentException,提示无法评估。如果我注释掉具有$group子句的那一行,则可以看到aggregation变量的toString()表示形式,其中揭示了$project子句存在问题:
{
  "aggregate" : "__collection__" ,
  "pipeline" : [
    { "$match" : { "_id" : { "$in" : [ "A" , "B" , "C"]}}} ,
    { "$project" : { "versions" : { "versions" : "$nested.field.version"}}} ,
    { "$unwind" : "$versions"}
  ]
}

显然,这与我的意图不符,所以我没有正确使用语法。但是说实话,我不认为Spring MongoOps的语法很直观,他们的文档也不是很好。

我没有看到任何调用nested()方法的方法,而不先包含对and()的调用。我认为这是主要问题,因为它会使嵌套加倍。有没有Spring MongoOps英雄可以帮助我正确地编写等效的Java代码?

编辑:这是我正在使用的集合的快照:robomongo display

3个回答

7

在此聚合管道中,$project 管道不是必需的,因为您仍然可以对嵌套字段执行$unwind操作,因此此聚合管道可以产生与当前聚合管道相同的结果:

db.MyCollection.aggregate([
    {
        "$match": {
            "_id": { "$in": ['A', 'B', 'C'] }
        }
    },
    { "$unwind": "$nested.field" },
    {
        "$group": {
            "_id": "$_id", 
            "maxVersion": { "$max": "$nested.field.version" }
        }
    }
])

Spring Data MongoDB的聚合等效功能:

Aggregation agg = newAggregation(
        match(Criteria.where("_id").in(ids)),
        unwind("nested.field"),        
        group("_id").max("nested.field.version").as("maxVersion")
    );

回到你当前的聚合操作,你需要在 nested.field 数组上执行$unwind 操作,而不是在 nested.field.version 字段上进行操作,因为那是一个字符串,而不是数组:

db.MyCollection.aggregate([
    {$match: {_id: {$in: ['A', 'B', 'C']}}},
    {$project: {"fields": "$nested.field"}},
    {$unwind: "$fields"},
    {$group: {_id: "$_id", "maxVersion": {$max: "$fields.version"}}}
])

Spring Data MongoDB 的等效代码如下:

Aggregation agg = newAggregation(
        match(Criteria.where("_id").in(ids)),
        project().and("nested.field").as("fields")
        unwind("fields"),        
        group("_id").max("fields.version").as("maxVersion")
    );

2
很好,你在这里的回答是正确的。不幸的是,它在我的特定情况下行不通,因为Spring Data存在一个bug。在发帖到SO之前,我总是对字段名称进行清理,但实际上,在一些嵌套的字段名称中有下划线,并且看起来Spring Data在进行某些引用完整性检查时会对下划线进行分割,这导致它失败。所以感谢你的努力,但不幸的是,Spring Data现在对于我的用例来说太过有缺陷了。 - soapergem
@SoaperGEM 不用担心,很遗憾那对你没起作用。 - chridam

0

Spring在进行聚合操作时,对于数组使用_作为通配符,并拆分snake_case字段进行字段引用验证。

为了避免验证,您可以使用以下MongoTemplate方法执行聚合,而不进行字段转换和验证。

public <O> AggregationResults<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType)

0

在下划线错误修复之前,使用MapReduce方式。

GroupBy groupBy = GroupBy.key("user_id")
        .initialDocument("{ total : 0, used : 0 }")
        .reduceFunction("function( curr, result ){ result.total++; if(curr.status == 1) {result.used++;} result.userID = curr.user_id;");
        GroupByResults<YourResult> yourResultInfo =
                mongoTemplate.group(Criteria.where("user_id").in(user_ids),
                                "your_collection_name", groupBy, YourResult.class);

class YourResult{
private String userID;
    private Long total = 0l;
    private Long used = 0l;
// getter and setter`enter code here
}

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