MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Campi raggruppati calcolati in MongoDB

In realtà puoi fare qualcosa del genere prima con "progetto", ma per me è un po' controintuitivo richiedere un $project fase in anticipo:

    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")
    );

Come ho detto, contro-intuitivo dovresti essere in grado di dichiarare tutto questo in $group fase, ma gli aiutanti non sembrano funzionare in questo modo. La serializzazione risulta un po' divertente (avvolge gli argomenti dell'operatore di data con gli array) ma sembra funzionare. Tuttavia, si tratta di due fasi della pipeline anziché di una.

Qual è il problema con questo? Ebbene, separando le fasi le fasi la parte "progetto" forza l'elaborazione di tutti i documenti in cantiere per ottenere i campi calcolati, cioè passa attraverso tutto prima di passare alla fase a gironi.

La differenza nel tempo di elaborazione può essere vista chiaramente eseguendo le query in entrambi i moduli. Con una fase di progetto separata, sul mio hardware l'esecuzione richiede tre volte più tempo rispetto alla query in cui tutti i campi vengono calcolati durante l'operazione di "gruppo".

Quindi sembra che l'unico modo attuale per costruirlo correttamente sia costruire tu stesso l'oggetto 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);

O se tutto ciò ti sembra conciso, puoi sempre lavorare con il sorgente JSON e analizzarlo. Ma ovviamente deve essere JSON valido:

    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);