MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Campos group-by calculados no MongoDB


Você pode realmente fazer algo assim com "projeto" primeiro, mas para mim é um pouco contra-intuitivo exigir um $project estágio antes da mão:
    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")
    );

Como eu disse, contra-intuitivo, você deve ser capaz de declarar tudo isso em $group palco, mas os ajudantes não parecem funcionar dessa maneira. A serialização sai um pouco engraçada (envolve os argumentos do operador de data com arrays), mas parece funcionar. Mas ainda assim, são dois estágios de pipeline em vez de um.

Qual é o problema com isso? Pois separando as etapas das etapas a parte "projeto" força o processamento de todos os documentos no pipeline para obter os campos calculados, ou seja, passa por tudo antes de passar para a fase de grupos.

A diferença no tempo de processamento pode ser vista claramente executando as consultas em ambas as formas. Com um estágio de projeto separado, no meu hardware demora três vezes mais para executar do que a consulta onde todos os campos são calculados durante a operação de "grupo".

Portanto, parece que a única maneira atual de construir isso corretamente é construindo você mesmo o objeto pipeline:
    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);

Ou se tudo isso parece conciso para você, você sempre pode trabalhar com a fonte JSON e analisá-la. Mas claro, tem que ser um JSON válido:
    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);