MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Berechnete Group-By-Felder in MongoDB

Sie können so etwas tatsächlich zuerst mit "project" machen, aber für mich ist es ein wenig kontraintuitiv, ein $project zu verlangen Stufe vorher:

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

Wie ich schon sagte, kontraintuitiv, da Sie all dies einfach unter $group deklarieren können sollten Bühne, aber die Helfer scheinen nicht so zu funktionieren. Die Serialisierung kommt etwas komisch heraus (verpackt die Argumente des Datumsoperators mit Arrays), aber es scheint zu funktionieren. Aber dennoch sind dies zwei Pipeline-Stufen und nicht eine.

Was ist das Problem dabei? Nun, durch die Trennung der Phasen erzwingt der „Projekt“-Teil die Verarbeitung aller Dokumente in der Pipeline, um die berechneten Felder zu erhalten, das heißt, er durchläuft alles, bevor er zur Gruppenphase übergeht.

Der Unterschied in der Verarbeitungszeit ist deutlich erkennbar, wenn die Abfragen in beiden Formen ausgeführt werden. Mit einer separaten Projektstufe dauert die Ausführung auf meiner Hardware dreimal länger als die Abfrage, bei der alle Felder während der "Gruppen"-Operation berechnet werden.

Es scheint also, dass die einzige Möglichkeit, dies richtig zu konstruieren, darin besteht, das Pipeline-Objekt selbst zu erstellen:

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

Oder wenn Ihnen das alles zu knapp erscheint, können Sie immer mit der JSON-Quelle arbeiten und diese analysieren. Aber natürlich muss es gültiges JSON sein:

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