sql >> Database >  >> NoSQL >> MongoDB

Groepeer verschillende waarden en tellingen voor elke eigenschap in één zoekopdracht

Er zijn verschillende benaderingen, afhankelijk van de beschikbare versie, maar ze komen allemaal neer op het transformeren van uw documentvelden in afzonderlijke documenten in een "array", en het vervolgens "afwikkelen" van die array met $unwind en het doen van opeenvolgende $group om de outputtotalen en arrays te accumuleren.

MongoDB 3.4.4 en hoger

De nieuwste releases hebben speciale operators zoals $arrayToObject en $objectToArray die de overdracht naar de initiële "array" van het brondocument dynamischer kan maken dan in eerdere releases:

db.profile.aggregate([
  { "$project": { 
     "_id": 0,
     "data": { 
       "$filter": {
         "input": { "$objectToArray": "$$ROOT" },
         "cond": { "$in": [ "$$this.k", ["gender","caste","education"] ] }
       }   
     }
  }},
  { "$unwind": "$data" },
  { "$group": {
    "_id": "$data",
    "total": { "$sum": 1 }  
  }},
  { "$group": {
    "_id": "$_id.k",
    "v": {
      "$push": { "name": "$_id.v", "total": "$total" } 
    }  
  }},
  { "$group": {
    "_id": null,
    "data": { "$push": { "k": "$_id", "v": "$v" } }
  }},
  { "$replaceRoot": {
    "newRoot": {
      "$arrayToObject": "$data"
    }
  }}
])

Dus met behulp van $objectToArray je maakt van het oorspronkelijke document een array van zijn sleutels en waarden als "k" en "v" sleutels in de resulterende array van objecten. We passen $filter toe hier om met "toets" te selecteren. Hier met behulp van $in met een lijst met sleutels die we willen, maar dit zou dynamischer kunnen worden gebruikt als een lijst met sleutels om te "uitsluiten" waar dat korter was. Het gebruikt gewoon logische operatoren om de toestand te evalueren.

De eindfase hier gebruikt $replaceRoot en aangezien al onze manipulatie en "groepering" ertussen nog steeds die "k" en "v" formulier gebruiken we dan $arrayToObject hier om onze "array of objects" in resultaat te promoten naar de "keys" van het document op het hoogste niveau in output.

MongoDB 3.6 $mergeObjects

Als een extra rimpel hier bevat MongoDB 3.6 $mergeObjects die kan worden gebruikt als een "accumulator " in een $group pijplijnfase ook, waardoor de $push en het maken van de laatste $replaceRoot eenvoudig de "data" verschuiven sleutel in plaats daarvan naar de "root" van het geretourneerde document:

db.profile.aggregate([
  { "$project": { 
     "_id": 0,
     "data": { 
       "$filter": {
         "input": { "$objectToArray": "$$ROOT" },
         "cond": { "$in": [ "$$this.k", ["gender","caste","education"] ] }
       }   
     }
  }},
  { "$unwind": "$data" },
  { "$group": { "_id": "$data", "total": { "$sum": 1 } }},
  { "$group": {
    "_id": "$_id.k",
    "v": {
      "$push": { "name": "$_id.v", "total": "$total" } 
    }  
  }},
  { "$group": {
    "_id": null,
    "data": {
      "$mergeObjects": {
        "$arrayToObject": [
          [{ "k": "$_id", "v": "$v" }]
        ] 
      }
    }  
  }},
  { "$replaceRoot": { "newRoot": "$data"  } }
])

Dit is niet zo heel anders dan wat in het algemeen wordt gedemonstreerd, maar laat eenvoudig zien hoe $mergeObjects kan op deze manier worden gebruikt en kan handig zijn in gevallen waarin de groeperingssleutel iets anders was en we die laatste "samenvoeging" met de hoofdruimte van het object niet wilden.

Merk op dat de $arrayToObject is nog steeds nodig om de "waarde" weer om te zetten in de naam van de "sleutel", maar we doen dit alleen tijdens de accumulatie in plaats van na de groepering, aangezien de nieuwe accumulatie het "samenvoegen" van sleutels mogelijk maakt.

MongoDB 3.2

Als je een versie terugneemt of zelfs als je een MongoDB 3.4.x hebt die minder is dan de 3.4.4-release, kunnen we nog steeds veel hiervan gebruiken, maar in plaats daarvan behandelen we het maken van de array ook op een meer statische manier omdat de uiteindelijke "transformatie" op uitvoer anders wordt behandeld vanwege de aggregatie-operators die we niet hebben:

db.profile.aggregate([
  { "$project": {
    "data": [
      { "k": "gender", "v": "$gender" },
      { "k": "caste", "v": "$caste" },
      { "k": "education", "v": "$education" }
    ]
  }},
  { "$unwind": "$data" },
  { "$group": {
    "_id": "$data",
    "total": { "$sum": 1 }  
  }},
  { "$group": {
    "_id": "$_id.k",
    "v": {
      "$push": { "name": "$_id.v", "total": "$total" } 
    }  
  }},
  { "$group": {
    "_id": null,
    "data": { "$push": { "k": "$_id", "v": "$v" } }
  }},
  /*
  { "$replaceRoot": {
    "newRoot": {
      "$arrayToObject": "$data"
    }
  }}
  */
]).map( d => 
  d.data.map( e => ({ [e.k]: e.v }) )
    .reduce((acc,curr) => Object.assign(acc,curr),{})
)

Dit is precies hetzelfde, behalve dat in plaats van een dynamische transformatie van het document in de array te hebben, we eigenlijk "expliciet" elk arraylid toewijzen met dezelfde "k" en "v" notatie. Eigenlijk alleen die sleutelnamen voor conventie op dit punt houden, omdat geen van de aggregatie-operators hier helemaal van afhankelijk is.

Ook in plaats van $replaceRoot , doen we precies hetzelfde als wat de vorige implementatie van de pijplijnfase daar deed, maar in plaats daarvan in clientcode. Alle MongoDB-stuurprogramma's hebben een implementatie van cursor.map() om "cursortransformaties" in te schakelen. Hier met de shell gebruiken we de basis JavaScript-functies van Array.map() en Array.reduce() om die uitvoer te nemen en de array-inhoud opnieuw te promoten tot de sleutels van het geretourneerde document op het hoogste niveau.

MongoDB 2.6

En terugvallend op MongoDB 2.6 om de tussenliggende versies te behandelen, is het enige dat hier verandert het gebruik van $map en een $literal voor invoer met de array-declaratie:

db.profile.aggregate([
  { "$project": {
    "data": {
      "$map": {
        "input": { "$literal": ["gender","caste", "education"] },
        "as": "k",
        "in": {
          "k": "$$k",
          "v": {
            "$cond": {
              "if": { "$eq": [ "$$k", "gender" ] },
              "then": "$gender",
              "else": {
                "$cond": {
                  "if": { "$eq": [ "$$k", "caste" ] },
                  "then": "$caste",
                  "else": "$education"
                }
              }    
            }
          }    
        }
      }
    }
  }},
  { "$unwind": "$data" },
  { "$group": {
    "_id": "$data",
    "total": { "$sum": 1 }  
  }},
  { "$group": {
    "_id": "$_id.k",
    "v": {
      "$push": { "name": "$_id.v", "total": "$total" } 
    }  
  }},
  { "$group": {
    "_id": null,
    "data": { "$push": { "k": "$_id", "v": "$v" } }
  }},
  /*
  { "$replaceRoot": {
    "newRoot": {
      "$arrayToObject": "$data"
    }
  }}
  */
])
.map( d => 
  d.data.map( e => ({ [e.k]: e.v }) )
    .reduce((acc,curr) => Object.assign(acc,curr),{})
)

Aangezien het basisidee hier is om een ​​verstrekte reeks veldnamen te "herhalen", wordt de daadwerkelijke toewijzing van waarden gedaan door de $cond verklaringen. Voor drie mogelijke uitkomsten betekent dit slechts een enkele nesting om voor elke uitkomst te "vertakken".

Moderne MongoDB vanaf 3.4 hebben $switch wat deze vertakking eenvoudiger maakt, maar dit toont aan dat de logica altijd mogelijk was en de $cond operator bestaat al sinds het aggregatieraamwerk werd geïntroduceerd in MongoDB 2.2.

Nogmaals, dezelfde transformatie op het cursorresultaat is van toepassing omdat er niets nieuws is en de meeste programmeertalen de mogelijkheid hebben om dit jarenlang te doen, zo niet vanaf het begin.

Natuurlijk kan het basisproces zelfs helemaal terug naar MongoDB 2.2 worden gedaan, maar alleen door het maken van de array toe te passen en $unwind op een andere manier. Maar niemand zou op dit moment een MongoDB onder 2.8 moeten draaien, en de officiële ondersteuning, zelfs vanaf 3.0, raakt zelfs snel op.

Uitvoer

Voor visualisatie heeft de uitvoer van alle gedemonstreerde pijplijnen hier de volgende vorm voordat de laatste "transformatie" is uitgevoerd:

/* 1 */
{
    "_id" : null,
    "data" : [ 
        {
            "k" : "gender",
            "v" : [ 
                {
                    "name" : "Male",
                    "total" : 3.0
                }, 
                {
                    "name" : "Female",
                    "total" : 2.0
                }
            ]
        }, 
        {
            "k" : "education",
            "v" : [ 
                {
                    "name" : "M.C.A",
                    "total" : 1.0
                }, 
                {
                    "name" : "B.E",
                    "total" : 3.0
                }, 
                {
                    "name" : "B.Com",
                    "total" : 1.0
                }
            ]
        }, 
        {
            "k" : "caste",
            "v" : [ 
                {
                    "name" : "Lingayath",
                    "total" : 3.0
                }, 
                {
                    "name" : "Vokkaliga",
                    "total" : 2.0
                }
            ]
        }
    ]
}

En dan ofwel door de $replaceRoot of de cursor transformeert zoals aangetoond, het resultaat wordt:

/* 1 */
{
    "gender" : [ 
        {
            "name" : "Male",
            "total" : 3.0
        }, 
        {
            "name" : "Female",
            "total" : 2.0
        }
    ],
    "education" : [ 
        {
            "name" : "M.C.A",
            "total" : 1.0
        }, 
        {
            "name" : "B.E",
            "total" : 3.0
        }, 
        {
            "name" : "B.Com",
            "total" : 1.0
        }
    ],
    "caste" : [ 
        {
            "name" : "Lingayath",
            "total" : 3.0
        }, 
        {
            "name" : "Vokkaliga",
            "total" : 2.0
        }
    ]
}

Dus hoewel we een aantal nieuwe en fraaie operators in de aggregatiepijplijn kunnen plaatsen waar we die beschikbaar hebben, is de meest voorkomende use-case in deze "einde van pijplijntransformaties", in welk geval we net zo goed dezelfde transformatie op elk document kunnen doen in de cursorresultaten keerden terug.




  1. Selderijfout:result.get time-out

  2. Veldaliassen met Mongoid en Rails

  3. Efficiënte paginering van MongoDB-aggregatie?

  4. redis lua-script versus enkele oproepen