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

Gruppér og tæl ved hjælp af aggregeringsramme

Det ser ud til, at du er kommet i gang med dette, men du er gået vild med nogle af de andre koncepter. Der er nogle grundlæggende sandheder, når du arbejder med arrays i dokumenter, men lad os starte, hvor du slap:

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

Så det er bare at bruge $group pipeline for at samle dine dokumenter på de forskellige værdier i "status"-feltet og derefter også producere et andet felt til "count", som selvfølgelig "tæller" forekomsterne af grupperingsnøglen ved at sende en værdi på 1 til $sum operatør for hvert fundne dokument. Dette bringer dig på et punkt, som du beskriver:

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

Det er den første fase af dette og let nok at forstå, men nu skal du vide, hvordan du får værdier ud af et array. Du kan så blive fristet, når du forstår "dot notation" koncept korrekt at gøre noget som dette:

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

Men hvad du vil finde er, at "totalen" faktisk vil være 0 for hvert af disse resultater:

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

Hvorfor? Nå, MongoDB-aggregationsoperationer som denne krydser faktisk ikke array-elementer, når de grupperes. For at gøre det har aggregeringsrammen et koncept kaldet $unwind . Navnet er relativt selvforklarende. Et indlejret array i MongoDB er meget som at have en "en-til-mange"-forbindelse mellem sammenkædede datakilder. Så hvad $unwind gør er præcis den slags "join"-resultater, hvor de resulterende "dokumenter" er baseret på indholdet af arrayet og duplikeret information for hver forælder.

Så for at kunne handle på array-elementer skal du bruge $unwind først. Dette burde logisk set føre dig til kode som denne:

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

Og så resultatet:

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

Men det er vel ikke helt rigtigt? Husk, hvad du lige har lært af $unwind og hvordan forbindes en denormaliseret med forældreinformationen? Så nu er det duplikeret for hvert dokument, da begge havde to array-medlemmer. Så selvom feltet "total" er korrekt, er "antal" dobbelt så meget, som det burde være i hvert tilfælde.

Der skal udvises lidt mere forsigtighed, så i stedet for at gøre dette i en enkelt $group trin udføres det i to:

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 korrekte totaler i:

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

Nu er tallene rigtige, men det er stadig ikke lige det, du efterspørger. Jeg vil synes, du skal stoppe der, da den slags resultat, du forventer, virkelig ikke er egnet til kun et enkelt resultat fra aggregering alene. Du leder efter, at totalen er "inde i" resultatet. Det hører egentlig ikke hjemme der, men på små data er det okay:

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" }
    }}
])

Og en endelig resultatformular:

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

Men "Gør det ikke" . MongoDB har en dokumentgrænse på svar på 16MB, hvilket er en begrænsning af BSON-specifikationen. På små resultater kan du lave denne form for bekvemmelighedsindpakning, men i det større system vil du have resultaterne i den tidligere form og enten en separat forespørgsel eller leve med at iterere hele resultaterne for at få totalen fra alle dokumenter.

Det ser ud til, at du bruger en MongoDB-version mindre end 2.6 eller kopierer output fra en RoboMongo-skal, som ikke understøtter de nyeste versionsfunktioner. Fra MongoDB 2.6 kan resultaterne af aggregering dog være en "markør" snarere end et enkelt BSON-array. Så den samlede respons kan være meget større end 16 MB, men kun når du ikke komprimerer til et enkelt dokument som resultater, vist for det sidste eksempel.

Dette vil især være tilfældet i tilfælde, hvor du "paging" resultaterne med 100- til 1000-vis af resultatlinjer, men du ville bare have et "total" til at returnere i et API-svar, når du kun returnerer en "side" med 25 resultater kl. en gang.

Under alle omstændigheder burde det give dig en rimelig guide til, hvordan du får den type resultater, du forventer fra din almindelige dokumentformular. Husk $unwind for at behandle arrays og generelt $group flere gange for at få totaler på forskellige grupperingsniveauer fra dine dokument- og samlingsgrupperinger.




  1. Er der nogen Redis-klient (Java foretrukket), som understøtter transaktioner på Redis-klyngen?

  2. Sådan serverer du mit indhold med nodejs vm på google cloud

  3. MongoDB:Får ikke korrekt resultat ved hjælp af $geoWithin-operatoren

  4. At finde et indlejret dokument af en specifik ejendom i Mongoose, Node.js, MongodDB