MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Grupuj odrębne wartości i liczby dla każdej właściwości w jednym zapytaniu

Istnieją różne podejścia w zależności od dostępnej wersji, ale wszystkie zasadniczo polegają na przekształceniu pól dokumentu w oddzielne dokumenty w „tablicy”, a następnie „rozwinięciu” tej tablicy za pomocą $unwind i wykonywanie kolejnych $group etapy w celu zgromadzenia sum wyjściowych i tablic.

MongoDB 3.4.4 i nowsze

Najnowsze wydania mają specjalne operatory, takie jak $arrayToObject i $objectToArray co może sprawić, że transfer do początkowej "tablicy" z dokumentu źródłowego będzie bardziej dynamiczny niż we wcześniejszych wydaniach:

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

Używając $objectToArray zamieniasz początkowy dokument w tablicę jego kluczy i wartości jako "k" i "v" klucze w wynikowej tablicy obiektów. Stosujemy $filter tutaj, aby wybrać za pomocą "klucza". Tutaj używając $in z listą kluczy, które chcemy, ale może to być bardziej dynamicznie używane jako lista kluczy do „wykluczenia” tych, które były krótsze. Po prostu używamy operatorów logicznych do oceny warunku.

Etap końcowy używa tutaj $replaceRoot a ponieważ cała nasza manipulacja i "grupowanie" pomiędzy nadal utrzymuje to "k" i "v" formularz, następnie używamy $arrayToObject tutaj, aby w rezultacie promować naszą „tablicę obiektów” do „kluczy” dokumentu najwyższego poziomu w wynikach.

MongoDB 3.6 $mergeObjects

Jako dodatkową zmarszczkę, MongoDB 3.6 zawiera $mergeObjects który może być używany jako "akumulator w $group również etap potoku, zastępując w ten sposób $push i tworzenie ostatecznego $replaceRoot po prostu przesuwając "dane" klucz do "głównego" zwróconego dokumentu:

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

Nie różni się to aż tak bardzo od tego, co zostało zaprezentowane ogólnie, ale po prostu pokazuje, jak $mergeObjects może być używany w ten sposób i może być przydatny w przypadkach, gdy klucz grupujący był czymś innym i nie chcieliśmy tego ostatecznego „scalenia” z przestrzenią główną obiektu.

Pamiętaj, że $arrayToObject jest nadal potrzebne do przekształcenia „wartości” z powrotem w nazwę „klucza”, ale robimy to po prostu podczas akumulacji, a nie po grupowaniu, ponieważ nowa akumulacja umożliwia „scalanie” kluczy.

MongoDB 3.2

Cofając się o wersję lub nawet jeśli masz MongoDB 3.4.x, która jest mniejsza niż wersja 3.4.4, nadal możemy jej używać, ale zamiast tego zajmujemy się tworzeniem tablicy w bardziej statyczny sposób jako sposób obsługi końcowej "transformacji" na wyjściu inaczej ze względu na operatory agregacji, których nie mamy:

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

To jest dokładnie to samo, z wyjątkiem tego, że zamiast dynamicznego przekształcania dokumentu w tablicę, w rzeczywistości "jawnie" przypisujemy każdemu elementowi tablicy ten sam "k" i "v" notacja. Naprawdę po prostu zachowaj te nazwy kluczy dla konwencji w tym momencie, ponieważ żaden z operatorów agregacji tutaj w ogóle nie zależy od tego.

Również zamiast używać $replaceRoot , robimy dokładnie to samo, co robiła tam poprzednia implementacja na etapie potoku, ale zamiast tego w kodzie klienta. Wszystkie sterowniki MongoDB mają implementację cursor.map() aby włączyć "przekształcenia kursora". Tutaj z powłoką używamy podstawowych funkcji JavaScript Array.map() i Array.reduce() aby wziąć to wyjście i ponownie promować zawartość tablicy jako klucze zwróconego dokumentu najwyższego poziomu.

MongoDB 2.6

Wracając do MongoDB 2.6, aby uwzględnić wersje pomiędzy nimi, jedyną rzeczą, która się tutaj zmienia, jest użycie $map i $literal dla danych wejściowych z deklaracją tablicy:

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

Ponieważ podstawową ideą jest tutaj „iteracja” dostarczonej tablicy nazw pól, faktyczne przypisanie wartości następuje przez „zagnieżdżenie” $cond sprawozdania. Dla trzech możliwych wyników oznacza to tylko jedno zagnieżdżenie w celu "rozgałęzienia" dla każdego wyniku.

Nowoczesne MongoDB z wersji 3.4 ma $switch co upraszcza to rozgałęzienie, ale pokazuje, że logika była zawsze możliwa i $cond operator istnieje od czasu wprowadzenia struktury agregacji w MongoDB 2.2.

Ponownie, ta sama transformacja na wyniku kursora ma zastosowanie, ponieważ nie ma tam nic nowego, a większość języków programowania ma możliwość robienia tego przez lata, jeśli nie od samego początku.

Oczywiście podstawowy proces można wykonać nawet z powrotem do MongoDB 2.2, ale po prostu stosując tworzenie tablicy i $unwind w inny sposób. Ale w tym momencie nikt nie powinien używać MongoDB w wersji 2.8, a oficjalne wsparcie nawet od wersji 3.0 szybko się wyczerpuje.

Wyjście

W celu wizualizacji dane wyjściowe wszystkich zademonstrowanych tu potoków mają następującą postać przed wykonaniem ostatniej „transformacji”:

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

A potem albo przez $replaceRoot lub kursor przekształci się, jak pokazano, wynik wygląda następująco:

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

Tak więc, chociaż możemy umieścić kilka nowych i fantazyjnych operatorów w potoku agregacji, w którym są one dostępne, najczęstszym przypadkiem użycia są te "transformacje końca potoku", w którym to przypadku równie dobrze możemy po prostu wykonać tę samą transformację na każdym dokumencie w zamiast tego zostały zwrócone wyniki kursora.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Czy można wyszukiwać poddokumenty bezpośrednio za pomocą mangusty?

  2. Najszybszy sposób na usunięcie duplikatów dokumentów w mongodb

  3. Jak zwrócić tylko wartość w MongoDB?

  4. sterownik c mongo:jak wyszukiwać dokumenty z _id na liście?

  5. Pola concat int i string, które znajdują się w różnych tablicach