【问题标题】:Aggregation Project Group By extracted day, month and year with Spring Data使用 Spring Data 按提取的日、月和年聚合项目组
【发布时间】:2015-12-23 16:47:52
【问题描述】:

直截了当,我该怎么做:

group._id = { 
    year: { $year : [{ $subtract: [ "$timestamp", 25200000 ]}] }, 
    month: { $month : [{ $subtract: [ "$timestamp", 25200000 ]}] }, 
    day: { $dayOfMonth : [{ $subtract: [ "$timestamp", 25200000 ]}] }
};

用弹簧数据

我已经尝试过这个和其他一些形式,但没有成功

Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(c),
                Aggregation.project("programa", "custo", "duracao", "dataHora")
                    .andExpression("dataHora").minus(25200000).extractDayOfMonth().as("dia")
                    .andExpression("dataHora").minus(25200000).extractMonth().as("mes")
                    .andExpression("dataHora").minus(25200000).extractYear().as("ano"),
                Aggregation.group("programa", "ano", "mes", "dia")
                    .count().as("count")
                    .sum("custo").as("valorTotal")
                    .sum("duracao").as("duracaoTotal")
                    .first("dataHora").as("dataHora"),
                Aggregation.sort(Direction.ASC, "dataHora")
        );

我需要在 mongodb 中按日、月和年分组,否则我需要在代码中转换所有这些分组数据。

提前致谢

【问题讨论】:

    标签: java mongodb mongodb-query aggregation-framework spring-mongo


    【解决方案1】:

    您在单个 $project 阶段中​​可以为字段计算执行的操作遇到了 spring mongo 的限制,实际上您可能已经将其编写为单独的 $project,因为您发现您无法投射自定义命名目前直接在$group _id 中的字段。

    因此,您最好将其全部保存在 $group 中,并使用不同的方法将调整后的日期四舍五入为当地时间。

    因此,编写$group 的更好方法是:

    { "$group": {
      "_id": {
        "programa": "$programa",
        "dataHora": {
          "$add": [
            { "$subtract": [
              { "$subtract": [{ "$subtract": ["$dataHora", new Date(0)] }, 25200000 ] },
              { "$mod": [
                { "$subtract": [{ "$subtract": ["$dataHora", new Date(0)] }, 25200000 ] },
                1000 * 60 * 60 * 24
              ]}
            ]},
            new Date(0)
          ]
        }
      },
      "count": { "$sum": 1 },
      "valorTotal": { "$sum": "$custo" },
      "duracaoTotal": { "$sum": "$duracao" },
      "dataHora": { "$first": "$dataHora" }
    }}
    

    当然,要在 spring-mongo 中使用这种结构,您需要一个聚合阶段操作的自定义实现,它可以采用定义的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);
        }
    }
    

    然后在这样的上下文中使用它:

        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(c),
                new CustomGroupOperation(
                    new BasicDBObject("$group",
                        new BasicDBObject("_id",
                            new BasicDBObject("programa","$programa")
                                .append("dataHora",
                                    new BasicDBObject("$add",Arrays.asList(
                                        new BasicDBObject("$subtract",Arrays.asList(
                                            new BasicDBObject("$subtract",Arrays.asList(
                                                new BasicDBObject("$subtract",Arrays.asList(
                                                    "$dataHora", new Date(0)
                                                )),
                                                25200000
                                            )),
                                            new BasicDBObject("$mod",Arrays.asList(
                                                new BasicDBObject("$subtract",Arrays.asList(
                                                    new BasicDBObject("$subtract",Arrays.asList(
                                                        "$dataHora", new Date(0)
                                                    )),
                                                    25200000
                                                )),
                                                1000 * 60 * 60 * 24
                                            ))
                                        )),
                                        new Date(0)
                                    ))
                                )
                        )
                        .append("count",new BasicDBObject("$sum",1))
                        .append("valorTotal",new BasicDBObject("$sum","$custo"))
                        .append("duracaoTotal",new BasicDBObject("$sum","$duracao"))
                        .append("dataHora",new BasicDBObject("$first","$dataHora"))
                    )
                ),
                Aggregation.sort(Direction.ASC,"_id.dataHora")
        );
    

    由于自定义类从内置辅助方法使用的同一基本类中抽象出来,因此可以与它们一起使用,如图所示。

    这里日期数学的基本过程是如何工作的,当您 $subtract 一个 BSON Date 对象与另一个对象时,结果是毫秒的差异,在这种情况下是从纪元日期( Date(0) )只是提取毫秒值。这使您可以进行数学运算,以一天中的毫秒数为模 ($mod) 舍入到当前日期值。

    就像您最初尝试的那样,当您将那个毫秒值 $add 发送到 BSON 日期对象时,返回的值又是一个 BSON 日期。因此,添加到表示 epoch 的对象会返回一个新的 Date 对象,但会四舍五入为当前日期。

    这通常比通过date aggregation operators 提取部分有用得多,而且代码也更短一些,尤其是当您在此处执行从 UTC 调整时间时。

    虽然这里$group 的构造比spring mongo 试图避免的辅助函数更简洁,但最终它比运行单独的$project 阶段来转换字段要高效得多无论如何,你真的只想要在 $group 阶段的值。

    【讨论】:

    • 非常感谢您的回答,也感谢您花时间解释mongo的限制和解决方法!我会尝试这种方法
    • 这个答案很震撼!
    猜你喜欢
    • 2015-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    相关资源
    最近更新 更多