【问题标题】:Calculated group-by fields in MongoDBMongoDB中计算的分组字段
【发布时间】:2014-10-15 18:21:45
【问题描述】:

对于 MongoDB 文档中的这个示例,如何使用 MongoTemplate 编写查询?

db.sales.aggregate(
   [
      {
        $group : {
           _id : { month: { $month: "$date" }, day: { $dayOfMonth: "$date" }, year: { $year: "$date" } },
           totalPrice: { $sum: { $multiply: [ "$price", "$quantity" ] } },
           averageQuantity: { $avg: "$quantity" },
           count: { $sum: 1 }
        }
      }
   ]
)

或者一般来说,我如何按计算字段分组?

【问题讨论】:

    标签: java mongodb spring-data aggregation-framework spring-mongodb


    【解决方案1】:

    你实际上可以先用“项目”做这样的事情,但对我来说,事先要求 $project 阶段有点违反直觉:

        Aggregation agg = newAggregation(
            project("quantity")
                .andExpression("dayOfMonth(date)").as("day")
                .andExpression("month(date)").as("month")
                .andExpression("year(date)").as("year")
                .andExpression("price * quantity").as("totalAmount"),
            group(fields().and("day").and("month").and("year"))
                .avg("quantity").as("averavgeQuantity")
                .sum("totalAmount").as("totalAmount")
                .count().as("count")
        );
    

    就像我说的那样,违反直觉,因为您应该能够在 $group 阶段声明所有这些,但助手似乎并没有以这种方式工作。序列化有点有趣(用数组包装日期运算符参数),但它似乎确实有效。但是,这仍然是两个流水线阶段,而不是一个。

    这有什么问题?好吧,通过将阶段分开,“项目”部分会强制处理管道中的所有文档以获得计算字段,这意味着它在进入小组阶段之前会通过所有内容。

    通过以两种形式运行查询可以清楚地看到处理时间的差异。使用单独的项目阶段,在我的硬件上执行的时间是在“组”操作期间计算所有字段的查询的三倍。

    因此,目前正确构建它的唯一方法似乎是自己构建管道对象:

        ApplicationContext ctx =
                new AnnotationConfigApplicationContext(SpringMongoConfig.class);
        MongoOperations mongoOperation = (MongoOperations) ctx.getBean("mongoTemplate");
    
        BasicDBList pipeline = new BasicDBList();
        String[] multiplier = { "$price", "$quantity" };
    
        pipeline.add(
            new BasicDBObject("$group",
                new BasicDBObject("_id",
                    new BasicDBObject("month", new BasicDBObject("$month", "$date"))
                        .append("day", new BasicDBObject("$dayOfMonth", "$date"))
                        .append("year", new BasicDBObject("$year", "$date"))
                )
                .append("totalPrice", new BasicDBObject(
                    "$sum", new BasicDBObject(
                        "$multiply", multiplier
                    )
                ))
                .append("averageQuantity", new BasicDBObject("$avg", "$quantity"))
                .append("count",new BasicDBObject("$sum",1))
            )
        );
    
        BasicDBObject aggregation = new BasicDBObject("aggregate","collection")
            .append("pipeline",pipeline);
    
        System.out.println(aggregation);
    
        CommandResult commandResult = mongoOperation.executeCommand(aggregation);
    

    或者,如果所有这些对您来说似乎都很简洁,那么您可以随时使用 JSON 源并对其进行解析。但当然,它必须是有效的 JSON:

        String json = "[" +
            "{ \"$group\": { "+
                "\"_id\": { " +
                    "\"month\": { \"$month\": \"$date\" }, " +
                    "\"day\": { \"$dayOfMonth\":\"$date\" }, " +
                    "\"year\": { \"$year\": \"$date\" } " +
                "}, " +
                "\"totalPrice\": { \"$sum\": { \"$multiply\": [ \"$price\", \"$quantity\" ] } }, " +
                "\"averageQuantity\": { \"$avg\": \"$quantity\" }, " +
                "\"count\": { \"$sum\": 1 } " +
            "}}" +
        "]";
    
        BasicDBList pipeline = (BasicDBList)com.mongodb.util.JSON.parse(json);
    

    【讨论】:

    • 感谢尼尔花时间回答这个问题。我实际上更喜欢您的第一个解决方案。那可能是因为我更熟悉关系数据库,而不是熟悉管道框架。
    • @NavinViswanath 在不同的阶段这样做是不好的。您正在创建额外的数据传递,这需要更多时间。在我的样品测试中是三倍。我在答案中对此进行了扩展,因为它有助于理解。
    • 我明白了。再次感谢尼尔。对于我正在编写的查询,它与这个非常相似,我在“项目”之前有一个“匹配”。我想这是另一个阶段,尽管匹配应该会显着过滤掉进入项目阶段的文档。
    【解决方案2】:

    另一种选择是使用如下定义的自定义聚合操作类:

    public class CustomAggregationOperation implements AggregationOperation {
        private DBObject operation;
    
        public CustomAggregationOperation (DBObject operation) {
            this.operation = operation;
        }
    
        @Override
        public DBObject toDBObject(AggregationOperationContext context) {
            return context.getMappedObject(operation);
        }
    }
    

    然后像这样在管道中实现它:

    Aggregation aggregation = newAggregation(
        new CustomAggregationOperation(
            new BasicDBObject(
                "$group",
                new BasicDBObject("_id",
                    new BasicDBObject("day", new BasicDBObject("$dayOfMonth", "$date" ))
                    .append("month", new BasicDBObject("$month", "$date"))
                    .append("year", new BasicDBObject("$year", "$date"))
                )
                .append("totalPrice", new BasicDBObject(
                    "$sum", new BasicDBObject(
                        "$multiply", Arrays.asList("$price","$quantity")
                    )
                ))
                .append("averageQuantity", new BasicDBObject("$avg", "$quantity"))
                .append("count",new BasicDBObject("$sum",1))
            )
        )
    )
    

    所以它基本上只是一个与现有管道助手使用的接口一致的接口,但不是使用其他助手方法,而是直接在管道构造中返回DBObject定义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-06-20
      • 2014-02-27
      • 2021-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-19
      • 2017-04-06
      相关资源
      最近更新 更多