sql >> Database >  >> NoSQL >> MongoDB

Mongodb geaggregeerde query, of te complex?

Hoewel het in uw vraag duidelijker had moeten zijn, suggereert uw uitvoervoorbeeld van de bron dat u op zoek bent naar:

  • Totaal aantal berichten per "uid"
  • Verschillend aantal waarden in "naar"
  • Verschillend aantal waarden in "van"
  • Samenvatting van tellingen per "uur" voor elke "uid"

Dit is allemaal mogelijk in een enkele aggregatieverklaring, en er is alleen wat zorgvuldig beheer van de afzonderlijke lijsten nodig en vervolgens enige manipulatie om de resultaten voor elk uur in een periode van 24 uur in kaart te brengen.

De beste aanpak hier wordt geholpen door operators die zijn geïntroduceerd in MongoDB 3.2:

db.collection.aggregate([
    // First group by hour within "uid" and keep distinct "to" and "from"
    { "$group": {
        "_id": {
            "uid": "$uid",
            "time": { "$hour": "$timestamp" }
        },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "count": { "$sum": 1 }
    }},

    // Roll-up to "uid" and keep each hour in an array
    { "$group": {
        "_id": "$_id.uid",
        "total": { "$sum": "$count" },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "temp_hours": { 
            "$push": {
                "index": "$_id.time",
                "count": "$count"
            }
        }
     }},

     // Getting distinct "to" and "from" requires a double unwind of arrays
     { "$unwind": "$to" },
     { "$unwind": "$to" },
     { "$unwind": "$from" },
     { "$unwind": "$from" },

     // And then adding back to sets for distinct
     { "$group": {
        "_id": "$_id",
        "total": { "$first": "$total" },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "temp_hours": { "$first": "$temp_hours" }
     }},

     // Map out for each hour and count size of distinct lists
     { "$project": {
        "count": "$total",
        "from_count": { "$size": "$from" },
        "to_count": { "$size": "$to" },
        "hours": {
            "$map": {
                "input": [
                     00,01,02,03,04,05,06,07,08,09,10,11,
                     12,13,14,15,16,17,18,19,20,21,22,23
                 ],
                 "as": "el",
                 "in": {
                      "$ifNull": [
                          { "$arrayElemAt": [
                              { "$map": {
                                  "input": { "$filter": {
                                     "input": "$temp_hours",
                                     "as": "tmp",
                                     "cond": {
                                         "$eq": [ "$$el", "$$tmp.index" ]
                                     }
                                  }},
                                 "as": "out",
                                 "in": "$$out.count"
                              }},
                              0
                          ]},
                          0
                      ]
                 }
            }
        }
     }},

     // Optionally sort in "uid" order
     { "$sort": { "_id": 1 } }
 ])

Voorafgaand aan MongoDB 3.2 moest je wat meer betrokken raken om de array-inhoud voor alle uren van de dag in kaart te brengen:

db.collection.aggregate([

    // First group by hour within "uid" and keep distinct "to" and "from"
    { "$group": {
        "_id": {
            "uid": "$uid",
            "time": { "$hour": "$timestamp" }
        },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "count": { "$sum": 1 }
    }},

    // Roll-up to "uid" and keep each hour in an array
    { "$group": {
        "_id": "$_id.uid",
        "total": { "$sum": "$count" },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "temp_hours": { 
            "$push": {
                "index": "$_id.time",
                "count": "$count"
            }
        }
     }},

     // Getting distinct "to" and "from" requires a double unwind of arrays
     { "$unwind": "$to" },
     { "$unwind": "$to" },
     { "$unwind": "$from" },
     { "$unwind": "$from" },

     // And then adding back to sets for distinct, also adding the indexes array
     { "$group": {
        "_id": "$_id",
        "total": { "$first": "$total" },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "temp_hours": { "$first": "$temp_hours" },
        "indexes": { "$first": { "$literal": [
                     00,01,02,03,04,05,06,07,08,09,10,11,
                     12,13,14,15,16,17,18,19,20,21,22,23
        ] } }
     }},

     // Denormalize both arrays
     { "$unwind": "$temp_hours" },
     { "$unwind": "$indexes" },

     // Marry up the index entries and keep either the value or 0
     // Note you are normalizing the double unwind to distinct index
     { "$group": {
         "_id": {
             "_id": "$_id",
             "index": "$indexes"
         },
         "total": { "$first": "$total" }, 
         "from": { "$first": "$from" },
         "to": { "$first": "$to" },
         "count": {
             "$max": {
                 "$cond": [
                     { "$eq": [ "$indexes", "$temp_hours.index" ] },
                     "$temp_hours.count",
                     0
                 ]
             }
         }
     }},

     // Sort to keep index order - !!Important!!         
     { "$sort": { "_id": 1 } },

     // Put the hours into the array and get sizes for other results
     { "$group": {
         "_id": "$_id._id",
         "count": { "$first": "$total" },
         "from_count": { "$first": { "$size": "$from" } },
         "to_count": { "$first": { "$size": "$to" } },
         "hours": { "$push": "$count" }
     }},

     // Optionally sort in "uid" order
     { "$sort": { "_id": 1 } }
])

Om dat op te splitsen, volgen beide benaderingen hier dezelfde basisstappen, met het enige echte verschil dat optreedt bij het in kaart brengen van "uren" voor de periode van 24 uur.

In de eerste aggregatie $group stadium is het doel om resultaten te krijgen per uur aanwezig in de gegevens en voor elke "uid"-waarde. De eenvoudige datumaggregatie-operator van $hour helpt deze waarde te verkrijgen als onderdeel van de groeperingssleutel.

De $addToSet bewerkingen zijn een soort "minigroep" op zich, en dit maakt het mogelijk om de "verschillende sets" voor elk van de "naar" en "van" waarden te behouden, terwijl ze in wezen nog steeds per uur groeperen.

De volgende $group is meer "organisatorisch", omdat de geregistreerde "tellingen" voor elk uur in een array worden bewaard terwijl alle gegevens worden opgerold om gewoon per "uid" te worden gegroepeerd. Dit geeft je in feite alle "gegevens" die je echt nodig hebt voor het resultaat, maar natuurlijk de $addToSet bewerkingen hier zijn gewoon het toevoegen van "arrays binnen arrays" van de verschillende sets die per uur worden bepaald.

Om deze waarden als echt afzonderlijke lijsten per "uid" en alleen te krijgen, is het noodzakelijk om elke array te deconstrueren met behulp van $unwind en dan uiteindelijk groeperen als alleen de verschillende "sets". Dezelfde $addToSet comprimeert dit, en de $first operaties nemen gewoon de "eerste" waarden van de andere velden, die al hetzelfde zijn voor de doel "per uid" gegevens. Daar zijn we blij mee, dus laat ze zoals ze zijn.

De laatste fase(n) hier zijn in wezen "cosmetisch" van aard en kunnen ook worden bereikt in de code aan de clientzijde. Aangezien er geen gegevens voor elk uurinterval aanwezig zijn, moeten deze worden toegewezen aan een reeks waarden die elk uur vertegenwoordigen. De twee benaderingen hier variëren afhankelijk van de mogelijkheden van de beschikbare operators tussen versies.

In de MongoDB 3.2-release zijn er $filter en $arrayElemAt operatoren waarmee u in feite de logica kunt creëren om een ​​invoerbron van alle mogelijke indexposities ( 24 uur ) te "transponeren" naar de waarden die al zijn bepaald voor de tellingen van die uren in de beschikbare gegevens. Dit is eigenlijk een "directe opzoeking" van waarden die al zijn geregistreerd voor elk beschikbaar uur om te zien of het bestaat, waar het wordt geteld, wordt getransponeerd naar de volledige array. Waar het niet aanwezig is, een standaardwaarde van 0 wordt op zijn plaats gebruikt.

Zonder deze operators betekent dit "op elkaar afstemmen" in wezen het de-normaliseren van beide arrays (de opgenomen gegevens en de volledige 24 posities) om te vergelijken en te transponeren. Dit is wat er gebeurt in de tweede benadering met een eenvoudige vergelijking van de "index" -waarden om te zien of er een resultaat was voor dat uur. De $max operator wordt hier voornamelijk gebruikt vanwege de twee $unwind statements, waarbij elke geregistreerde waarde uit de brongegevens wordt gereproduceerd voor elke mogelijke indexpositie. Dit "compact" tot alleen de gewenste waarden per "indexuur".

In die laatste benadering wordt het dan belangrijk om $sort op de groepering _id waarde. Dit komt omdat het de "index" -positie bevat, en dat zal nodig zijn wanneer deze inhoud terug wordt verplaatst naar een array waarvan u verwacht dat deze wordt geordend. Dat is natuurlijk de laatste $group stap hier waar de geordende posities in een array worden geplaatst met $push .

Terug naar de "verschillende lijsten", de $size operator wordt in alle gevallen gebruikt om de "lengte" en dus het "aantal" van verschillende waarden in de lijsten voor "tot" en "van" te bepalen. Dit is tenminste de enige echte beperking op MongoDB 2.6, maar kan anders worden vervangen door eenvoudig elke array afzonderlijk te "afwikkelen" en vervolgens weer te groeperen op de _id al aanwezig om de array-items in elke set te tellen. Het is een basisproces, maar zoals je zou moeten zien, is de $size operator is hier de betere optie voor algemene prestaties.

Als laatste opmerking, uw conclusiegegevens zijn een beetje afwijkend, omdat mogelijk de invoer met "ddd" in "van" ook bedoeld was om hetzelfde te zijn in "naar", maar in plaats daarvan is vastgelegd als "bbb". Dit verandert de afzonderlijke telling van de derde "uid"-groepering voor "naar" met één invoer. Maar natuurlijk zijn de logische resultaten gezien de brongegevens goed:

{ "_id" : 1000000, "count" : 3, "from_count" : 2, "to_count" : 2, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0 ] }
{ "_id" : 2000000, "count" : 2, "from_count" : 1, "to_count" : 1, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0 ] }
{ "_id" : 3000000, "count" : 5, "from_count" : 5, "to_count" : 4, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0 ] }

N.B. De bron heeft ook een typfout met het scheidingsteken tussen : in plaats van een komma direct na het tijdstempel op alle regels.




  1. Hoe unie-query's in mongoDB te schrijven?

  2. Hoe u hoofdletterongevoelige indexen maakt in MongoDB

  3. MongoDB-updategegevens in genest veld

  4. MongoDB toArray-prestaties