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

Gruppér forskellige værdier og tæller for hver ejendom i én forespørgsel

Der er forskellige tilgange afhængigt af den tilgængelige version, men de opdeles i det væsentlige til at transformere dine dokumentfelter til separate dokumenter i et "array" og derefter "afvikle" det array med $unwind og gør successive $group trin for at akkumulere outputtotaler og arrays.

MongoDB 3.4.4 og nyere

Seneste udgivelser har specielle operatorer som $arrayToObject og $objectToArray som kan gøre overførslen til det oprindelige "array" fra kildedokumentet mere dynamisk end i tidligere udgivelser:

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

Så ved at bruge $objectToArray du gør det indledende dokument til en række af dets nøgler og værdier som "k" og "v" nøgler i den resulterende række af objekter. Vi anvender $filter her for at vælge med "tast". Her bruger du $in med en liste over nøgler, vi ønsker, men dette kunne bruges mere dynamisk som en liste over nøgler til at "udelukke", hvor det var kortere. Det er bare at bruge logiske operatorer til at evaluere tilstanden.

Slutstadiet her bruger $replaceRoot og da al vores manipulation og "gruppering" derimellem stadig holder "k" og "v" formular, bruger vi derefter $arrayToObject her for at promovere vores "array af objekter" som resultat til "nøglerne" af dokumentet på øverste niveau i output.

MongoDB 3.6 $mergeObjects

Som en ekstra rynke her inkluderer MongoDB 3.6 $mergeObjects som kan bruges som en "akkumulator" " i en $group også pipeline-stadiet og erstatter dermed $push og lave den sidste $replaceRoot simpelthen at flytte "data" nøgle til "roden" af det returnerede dokument i stedet:

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

Dette er egentlig ikke så forskelligt fra det, der bliver demonstreret overordnet, men demonstrerer blot, hvordan $mergeObjects kan bruges på denne måde og kan være nyttig i tilfælde, hvor grupperingsnøglen var noget andet, og vi ikke ønskede den endelige "fletning" til objektets rodrum.

Bemærk, at $arrayToObject er stadig nødvendig for at omdanne "værdien" tilbage til navnet på "nøglen", men vi gør det bare under akkumuleringen i stedet for efter grupperingen, da den nye akkumulering tillader "fusion" af nøgler.

MongoDB 3.2

Tager det en version tilbage, eller selvom du har en MongoDB 3.4.x, der er mindre end 3.4.4-udgivelsen, kan vi stadig bruge meget af dette, men i stedet beskæftiger vi os også med oprettelsen af ​​arrayet på en mere statisk måde. som at håndtere den endelige "transformation" på output anderledes på grund af de aggregeringsoperatorer, vi ikke har:

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),{})
)

Dette er nøjagtig det samme, bortset fra at i stedet for at have en dynamisk transformation af dokumentet til arrayet, tildeler vi faktisk "eksplicit" hvert array-medlem den samme "k" og "v" notation. Beholder egentlig bare disse nøglenavne for konventionen på dette tidspunkt, da ingen af ​​aggregeringsoperatørerne her overhovedet er afhængige af det.

Også i stedet for at bruge $replaceRoot , vi gør præcis det samme, som den forrige implementering af pipelinestadiet gjorde der, men i klientkoden i stedet for. Alle MongoDB-drivere har en vis implementering af cursor.map() for at aktivere "markørtransformationer". Her med skallen bruger vi de grundlæggende JavaScript-funktioner i Array.map() og Array.reduce() at tage det output og igen fremme matrixindholdet til at være nøglerne til det returnerede dokument på øverste niveau.

MongoDB 2.6

Og falder vi tilbage til MongoDB 2.6 for at dække versionerne imellem, er det eneste, der ændrer sig her brugen af ​​$map og en $literal til input med array-deklarationen:

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),{})
)

Da den grundlæggende idé her er at "iterere" et givet array af feltnavne, kommer den faktiske tildeling af værdier ved at "nesting" $cond udsagn. For tre mulige udfald betyder dette kun en enkelt indlejring for at "grene" for hvert udfald.

Moderne MongoDB fra 3.4 har $switch hvilket gør denne forgrening enklere, men dette viser, at logikken altid var mulig, og $cond operatør har eksisteret siden aggregeringsrammen blev introduceret i MongoDB 2.2.

Igen gælder den samme transformation på markørresultatet, da der ikke er noget nyt der, og de fleste programmeringssprog har evnen til at gøre dette i årevis, hvis ikke fra starten.

Selvfølgelig kan den grundlæggende proces endda udføres helt tilbage til MongoDB 2.2, men bare ved at anvende array-oprettelse og $unwind på en anden måde. Men ingen burde køre MongoDB under 2.8 på dette tidspunkt, og officiel support selv fra 3.0 er endda hurtigt ved at løbe tør.

Output

Til visualisering har outputtet af alle demonstrerede pipelines her følgende form, før den sidste "transformation" er udført:

/* 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
                }
            ]
        }
    ]
}

Og derefter enten ved $replaceRoot eller markørtransformationen som vist bliver resultatet:

/* 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
        }
    ]
}

Så selvom vi kan sætte nogle nye og smarte operatører ind i aggregeringspipelinen, hvor vi har dem tilgængelige, er den mest almindelige brugssag i disse "end of pipeline-transformationer", i hvilket tilfælde vi lige så godt kan lave den samme transformation på hvert dokument i markørresultaterne returnerede i stedet.




  1. Django Selleri få opgavetælling

  2. Tjek, om MongoDB upsert foretog en indsættelse eller en opdatering

  3. Beskyttelse af dine data med ClusterControl

  4. Sådan gør du:Brug HBase Thrift Interface, del 2:Indsættelse/hentning af rækker