MongoTemplate 条件聚合函数

4

我有一个集合,其中文档的格式如下:

{
  _id: "545b9fa0dd5318a4285f7ce7",
  owner: "admin",  
  messages: [
    {
      id: "100",
      status: "sent",
      note: ""
    },
    {
      id: "100",
      status: "pending",
      note: ""
    },
    {
      id: "101",
      status: "sent",
      note: ""
    },
    {
      id: "102",
      status: "sent",
      note: ""
    },
    {
      id: "101",
      status: "done",
      note: ""
    }
  ]
}

这只是一个简短的例子,实际情况中我的子数组非常大。

我需要查询集合并获取特定文档的一些统计信息。因此,在此示例中,如果我查询具有ID:"545b9fa0dd5318a4285f7ce7"的文档,我应该得到以下结果:

{
   sent: 3,
   pending: 1,
   done: 1
}

我该如何使用Spring MongoTemplate进行聚合操作?
2个回答

8
要做这种事情,您需要在聚合框架中使用$cond运算符。Spring Data MongoDB目前还没有实现这一点,而常见的$group操作也缺少许多内容,甚至只在$project下实现。
跟踪任何$cond支持的问题请参阅此处:

https://jira.spring.io/browse/DATAMONGO-861

对于世界上的其他人来说,它看起来像这样:
db.collection.aggregate([
    { "$match": { "_id": ObjectId("545b9fa0dd5318a4285f7ce7") } },
    { "$unwind": "$messages" },
    { "$group": {
        "_id": "$_id",
        "sent": {
            "$sum": {
                "$cond": [
                    { "$eq": [ "$mesages.status", "sent" ] },
                    1,
                    0
                ]
            }
        },
        "pending": {
            "$sum": {
                "$cond": [
                    { "$eq": [ "$messages.status", "pending" ] },
                    1,
                    0
                ]
            }
        },
        "done": {
            "$sum": {
                "$cond": [
                    { "$eq": [ "$messages.status", "done" ] },
                    1,
                    0
                ]
            }
        }
    }}
])

要在mongotemplate聚合下使这样的事情工作,您需要扩展聚合操作的类,该类可以从DBObject构建:
public class CustomGroupOperation implements AggregationOperation {
    private DBObject operation;

    public CustomGroupOperation (DBObject operation) {
        this.operation = operation;
    }

    @Override
    public DBObject toDBObject(AggregationOperationContext context) {
        return context.getMappedObject(operation);
    }
}

然后您可以将"$group"定义为DBObject并在聚合管道中实现:

   DBObject myGroup = (DBObject)new BasicDBObject(
        "$group", new BasicDBObject(
            "_id","$_id"
        ).append(
            "sent", new BasicDBObject(
                "$sum", new BasicDBObject(
                    "$cond", new Object[]{
                        new BasicDBObject(
                            "$eq", new Object[]{ "$messages.status", "sent"}
                        ),
                        1,
                        0
                    }
                )
            )
        ).append(
            "pending", new BasicDBObject(
                "$sum", new BasicDBObject(
                    "$cond", new Object[]{
                        new BasicDBObject(
                            "$eq", new Object[]{ "$messages.status", "pending"}
                        ),
                        1,
                        0
                    }
                )
             )
        ).append(
            "done", new BasicDBObject(
                "$sum", new BasicDBObject(
                    "$cond", new Object[]{
                         new BasicDBObject(
                            "$eq", new Object[]{ "$messages.status", "done"}
                         ),
                         1,
                         0
                    }
                 )
            )
        )
     );


   ObjectId myId = new ObjectId("545b9fa0dd5318a4285f7ce7");

   Aggregation aggregation = newAggregation(
           match(Criteria.where("_id").is(myId)),
           unwind("messges"),
           new CustomGroupOperation(myGroup)
   );

这使您能够创建一个与上面的shell表示基本相同的管道。

因此,目前看来,在某些操作和序列不受支持的情况下,最好的情况是实现一个类在AgggregationOperation接口上,可以将DBObject提供给它,或者通过自己的自定义方法从内部构造一个。


你的解决方案非常好(只需要将$status更改为$messages.status),我希望mongotemplate能够尽快添加更多的聚合API,因为这并不是很优雅。 - jacob
@jacob 抱歉,是我不好。打得太快了。我会编辑以备后用。我重新标记了你的问题,标记为我知道“spring-data”维护者定期查看的内容。$cond 问题已经存在一段时间了。复杂分组和分组 _id 值是“已知问题”。其中一些实现起来并不难作为方法。你可以随时捐出时间。我也应该考虑这个。 - Neil Lunn
@NeilLunn 我该怎么匹配两个条件呢?比如我想匹配 messages.status=="Done"messages.status2=="Ready" - Half Blood Prince
@NeilLunn 请展示 matchunwind 方法的实现。 - Half Blood Prince
@NeilLunn 我找不到指定集合名称的地方。你能分享完整的代码吗? - Half Blood Prince
这是2020年。有更好的解决方案吗?我不想扩展类。还有其他的解决方案吗? - Manan Shah

0
您可以使用以下聚合方式:
db.collection.aggregate(
    { $match : { "_id" : ObjectId("545b9fa0dd5318a4285f7ce7") } },
    { $unwind : "$messages" },
    { $group : { "_id" : "$messages.status", "count" : { $sum : 1} } }
)

它将为您提供状态的计数,其中消息可用,所有其他状态计数应视为0。


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