sql >> Databasteknik >  >> NoSQL >> MongoDB

Gruppera och räkna med aggregeringsramverk

Det verkar som om du har börjat med det här, men du har gått vilse med några av de andra koncepten. Det finns några grundläggande sanningar när du arbetar med arrayer i dokument, men låt oss börja där du slutade:

db.sample.aggregate([
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 }
    }}
])

Så det är bara att använda $group pipeline för att samla upp dina dokument på de olika värdena i "status"-fältet och sedan även producera ett annat fält för "count" som naturligtvis "räknar" förekomsterna av grupperingsnyckeln genom att skicka ett värde på 1 till $sum operatör för varje hittat dokument. Detta placerar dig vid en punkt ungefär som du beskriver:

{ "_id" : "done", "count" : 2 }
{ "_id" : "canceled", "count" : 1 }

Det är det första steget i detta och tillräckligt lätt att förstå, men nu måste du veta hur du får ut värden ur en array. Du kan då bli frestad när du förstår "dot notation" koncept korrekt för att göra något så här:

db.sample.aggregate([
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$devices.cost" }
    }}
])

Men vad du kommer att upptäcka är att den "totala" faktiskt kommer att vara 0 för vart och ett av dessa resultat:

{ "_id" : "done", "count" : 2, "total" : 0 }
{ "_id" : "canceled", "count" : 1, "total" : 0 }

Varför? Tja, MongoDB-aggregationsoperationer som denna går faktiskt inte igenom arrayelement vid gruppering. För att göra det har aggregeringsramverket ett koncept som heter $unwind . Namnet är relativt självförklarande. En inbäddad array i MongoDB är ungefär som att ha en "en-till-många"-association mellan länkade datakällor. Så vad $unwind gör är exakt den sortens "join"-resultat, där de resulterande "dokumenten" baseras på innehållet i arrayen och duplicerad information för varje förälder.

Så för att kunna agera på arrayelement måste du använda $unwind först. Detta borde logiskt leda dig till kod så här:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$devices.cost" }
    }}
])

Och sedan resultatet:

{ "_id" : "done", "count" : 4, "total" : 700 }
{ "_id" : "canceled", "count" : 2, "total" : 350 }

Men det är väl inte helt rätt? Kom ihåg vad du just lärde dig från $unwind och hur förenas en avnormaliserad information med föräldrainformationen? Så nu dupliceras det för varje dokument eftersom båda hade två arraymedlemmar. Så även om fältet "totalt" är korrekt, är "antal" dubbelt så mycket som det borde vara i varje fall.

Lite mer försiktighet måste iakttas, så istället för att göra detta i en enda $group steg görs det i två:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$_id",
        "status": { "$first": "$status" },
        "total": { "$sum": "$devices.cost" }
    }},
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$total" }
    }}
])

Som nu får resultatet med korrekta summor i sig:

{ "_id" : "canceled", "count" : 1, "total" : 350 }
{ "_id" : "done", "count" : 2, "total" : 700 }

Nu stämmer siffrorna, men det är fortfarande inte exakt vad du efterfrågar. Jag skulle tycka att du borde sluta där eftersom den typ av resultat du förväntar dig verkligen inte lämpar sig för bara ett enda resultat från enbart aggregering. Du letar efter att summan ska vara "inne i" resultatet. Det hör verkligen inte hemma där, men på små data är det okej:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$_id",
        "status": { "$first": "$status" },
        "total": { "$sum": "$devices.cost" }
    }},
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$total" }
    }},
    { "$group": {
        "_id": null,
        "data": { "$push": { "count": "$count", "total": "$total" } },
        "totalCost": { "$sum": "$total" }
    }}
])

Och ett slutresultatformulär:

{
    "_id" : null,
    "data" : [
            {
                    "count" : 1,
                    "total" : 350
            },
            {
                    "count" : 2,
                    "total" : 700
            }
    ],
    "totalCost" : 1050
}

Men, "Gör inte det" . MongoDB har en dokumentgräns för svar på 16MB, vilket är en begränsning av BSON-specifikationen. På små resultat kan du göra den här typen av bekvämlighetsavveckling, men i det större schemat vill du ha resultaten i den tidigare formen och antingen en separat fråga eller leva med att iterera hela resultatet för att få summan från alla dokument.

Du verkar använda en MongoDB-version mindre än 2.6, eller kopiera utdata från ett RoboMongo-skal som inte stöder de senaste versionsfunktionerna. Från MongoDB 2.6 men resultaten av aggregering kan vara en "markör" snarare än en enda BSON-array. Så det totala svaret kan vara mycket större än 16 MB, men bara när du inte komprimerar till ett enda dokument som resultat, som visas för det sista exemplet.

Detta skulle vara särskilt sant i de fall där du "bloggade" resultaten, med 100-tals till 1000-tals resultatrader men du bara ville ha ett "totalt" att returnera i ett API-svar när du bara returnerar en "sida" med 25 resultat på en gång.

Hur som helst, det borde ge dig en rimlig guide om hur du får den typ av resultat du förväntar dig från ditt vanliga dokumentformulär. Kom ihåg $unwind för att bearbeta arrayer, och generellt $group flera gånger för att få totaler på olika grupperingsnivåer från dina dokument- och samlingsgrupperingar.




  1. Hur lagrar jag blobdata i MongoDB?

  2. Datetime-problem med Mongo och C#

  3. Finns det ett sätt att lagra python-objekt direkt i mongoDB utan att serialisera dem

  4. ELLER fråga som matchar noll eller med Mongoid fortfarande matchar?