sql >> Database >  >> NoSQL >> MongoDB

Groeperen en tellen met behulp van aggregatieframework

Het lijkt alsof je hiermee begonnen bent, maar je bent verdwaald bij een aantal van de andere concepten. Er zijn enkele basiswaarheden bij het werken met arrays in documenten, maar laten we beginnen waar u was gebleven:

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

Dus dat gaat gewoon de $group gebruiken pijplijn om uw documenten te verzamelen over de verschillende waarden van het "status"-veld en vervolgens ook een ander veld voor "count" te produceren, dat natuurlijk het aantal keren dat de groeperingssleutel voorkomt "telt" door een waarde van 1 naar de $sum operator voor elk gevonden document. Dit brengt je op een punt zoals je beschrijft:

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

Dat is de eerste fase hiervan en gemakkelijk genoeg om te begrijpen, maar nu moet je weten hoe je waarden uit een array haalt. U kunt dan in de verleiding komen als u de "puntnotatie" concept goed om zoiets als dit te doen:

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

Maar wat je zult vinden is dat het "totaal" in feite 0 . zal zijn voor elk van deze resultaten:

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

Waarom? Welnu, MongoDB-aggregatiebewerkingen zoals deze doorkruisen array-elementen niet echt bij het groeperen. Om dat te doen, heeft het aggregatieraamwerk een concept genaamd $unwind . De naam is relatief vanzelfsprekend. Een embedded array in MongoDB lijkt veel op het hebben van een "één-op-veel"-koppeling tussen gekoppelde gegevensbronnen. Dus wat $unwind doet is precies dat soort "join"-resultaat, waarbij de resulterende "documenten" zijn gebaseerd op de inhoud van de array en gedupliceerde informatie voor elke ouder.

Dus om op array-elementen te reageren, moet je $unwind eerst. Dit zou je logischerwijs naar de volgende code moeten leiden:

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

En dan het resultaat:

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

Maar dat klopt toch niet helemaal? Onthoud wat je zojuist hebt geleerd van $unwind en hoe werkt een gedenormaliseerde join met de bovenliggende informatie? Dus nu wordt dat gedupliceerd voor elk document, aangezien beide twee arrayleden hadden. Dus hoewel het veld "totaal" correct is, is de "telling" twee keer zoveel als in elk geval zou moeten zijn.

Er moet wat meer aandacht aan worden besteed, dus in plaats van dit in een enkele $groep fase, het is gedaan in twee:

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

Die nu het resultaat krijgt met de juiste totalen erin:

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

Nu zijn de cijfers goed, maar het is nog steeds niet precies wat je vraagt. Ik zou denken dat je daar moet stoppen, omdat het soort resultaat dat je verwacht echt niet geschikt is voor slechts een enkel resultaat van alleen aggregatie. U zoekt naar het totaal om "binnen" het resultaat te zijn. Het hoort daar echt niet thuis, maar op kleine data is het oké:

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

En een eindresultaatformulier:

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

Maar, "Doe dat niet" . MongoDB heeft een documentlimiet voor respons van 16 MB, wat een beperking is van de BSON-specificatie. Bij kleine resultaten kunt u dit soort gemaksverpakking doen, maar in het grotere geheel wilt u de resultaten in de eerdere vorm en ofwel een afzonderlijke query of leven met het herhalen van de hele resultaten om het totaal van alle documenten te krijgen.

Het lijkt erop dat u een MongoDB-versie van minder dan 2.6 gebruikt, of uitvoer kopieert van een RoboMongo-shell die de nieuwste versiefuncties niet ondersteunt. Vanaf MongoDB 2.6 kunnen de resultaten van aggregatie echter een "cursor" zijn in plaats van een enkele BSON-array. De algehele respons kan dus veel groter zijn dan 16 MB, maar alleen als u niet comprimeert tot een enkel document als resultaat, zoals weergegeven in het laatste voorbeeld.

Dit zou met name het geval zijn in gevallen waarin u de resultaten "pagingte", met 100 tot 1000 resultaatregels, maar u gewoon een "totaal" wilde retourneren in een API-antwoord wanneer u slechts een "pagina" van 25 resultaten retourneert op een keer.

Hoe dan ook, dat zou u een redelijke gids moeten geven over hoe u het type resultaten kunt krijgen dat u verwacht van uw gemeenschappelijke documentformulier. Onthoud $unwind om arrays te verwerken, en in het algemeen $group meerdere keren om totalen op verschillende groeperingsniveaus te krijgen uit uw document- en collectiegroeperingen.




  1. Hoe het mongo-commando uit te voeren met mongo-go-driver?

  2. Locaties opvragen binnen een straal in MongoDB

  3. Hoe eigenschappen koppelen aan socket.io-object in Redis Store?

  4. MongoDb-filterarray