MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Групирайте отделни стойности и бройки за всяко свойство в една заявка

Има различни подходи в зависимост от наличната версия, но всички те по същество се свеждат до трансформиране на вашите документни полета в отделни документи в „масив“, след което „отвиване“ на този масив с $unwind и извършване на последователни $group етапи, за да се натрупат изходните суми и масиви.

MongoDB 3.4.4 и по-нова версия

Последните версии имат специални оператори като $arrayToObject и $objectToArray което може да направи прехвърлянето към първоначалния "масив" от изходния документ по-динамично, отколкото в по-ранните версии:

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

Така че използвайки $objectToArray правите първоначалния документ в масив от неговите ключове и стойности като "k" и "v" ключове в резултантния масив от обекти. Прилагаме $filter тук, за да изберете по "ключ". Тук се използва $in със списък от ключове, които искаме, но това може да се използва по-динамично като списък от ключове за „изключване“, където това е по-кратко. Това е просто използване на логически оператори за оценка на условието.

Крайният етап тук използва $replaceRoot и тъй като цялата ни манипулация и "групиране" между тях все още запазва това "k" и "v" след това използваме $arrayToObject тук, за да популяризираме нашия „масив от обекти“ в резултат към „ключовете“ на документа от най-високо ниво в изхода.

MongoDB 3.6 $mergeObjects

Като допълнителна бръчка тук, MongoDB 3.6 включва $mergeObjects който може да се използва като "акумулатор " в $group етап на тръбопровод, като по този начин замества $push и прави окончателния $replaceRoot просто преместване на "данни" вместо това въведете „корена“ на върнатия документ:

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

Това всъщност не е толкова различно от това, което се демонстрира като цяло, а просто демонстрира как $mergeObjects може да се използва по този начин и може да бъде полезен в случаите, когато ключът за групиране е нещо различно и ние не искаме това окончателно „сливане“ към основното пространство на обекта.

Имайте предвид, че $arrayToObject все още е необходимо за преобразуване на „стойността“ обратно в името на „ключа“, но ние просто го правим по време на натрупването, а не след групирането, тъй като новото натрупване позволява „сливане“ на ключове.

MongoDB 3.2

Вземайки го обратно версия или дори ако имате MongoDB 3.4.x, която е по-малка от версията 3.4.4, все още можем да използваме голяма част от това, но вместо това се занимаваме със създаването на масива също по по-статичен начин като обработка на крайната "трансформация" на изхода по различен начин поради операторите за агрегиране, които нямаме:

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

Това е абсолютно същото, но вместо да имаме динамична трансформация на документа в масива, ние всъщност "изрично" присвояваме на всеки член на масива един и същ "k" и "v" нотация. Наистина просто запазвам тези ключови имена за конвенция в този момент, тъй като нито един от операторите за агрегиране тук изобщо не зависи от това.

Също така вместо да използвате $replaceRoot , ние просто правим абсолютно същото като това, което правеше там изпълнението на предишния етап на тръбопровода, но вместо това в клиентския код. Всички драйвери на MongoDB имат някаква реализация на cursor.map() за да разрешите "трансформиране на курсора". Тук с обвивката използваме основните функции на JavaScript на Array.map() и Array.reduce() за да вземе този изход и отново да повиши съдържанието на масива като ключове на върнатия документ от най-високо ниво.

MongoDB 2.6

И връщайки се към MongoDB 2.6, за да покрием версиите между тях, единственото нещо, което се променя тук, е използването на $map и $literal за въвеждане с декларацията на масива:

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

Тъй като основната идея тук е да се „итерира“ предоставен масив от имена на полета, действителното присвояване на стойности идва чрез „влагане“ на $cond изявления. За три възможни резултата това означава само едно влагане, за да се „разклони“ за всеки резултат.

Съвременната MongoDB от 3.4 има $switch което прави това разклоняване по-просто, но това демонстрира, че логиката винаги е била възможна и $cond оператор съществува, откакто рамката за агрегиране беше въведена в MongoDB 2.2.

Отново се прилага същата трансформация на резултата от курсора, тъй като там няма нищо ново и повечето езици за програмиране имат способността да правят това в продължение на години, ако не и от самото начало.

Разбира се, основният процес може дори да се извърши обратно към MongoDB 2.2, но само като се приложи създаването на масив и $unwind по различен начин. Но никой не трябва да работи с MongoDB под 2.8 в този момент, а официалната поддръжка дори от 3.0 бързо се изчерпва.

Изход

За визуализация изходът на всички демонстрирани тръбопроводи тук има следната форма, преди да бъде направена последната "трансформация":

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

И след това или чрез $replaceRoot или трансформацията на курсора, както е показано, резултатът става:

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

Така че докато можем да поставим някои нови и фантастични оператори в тръбопровода за агрегиране, където имаме такива, най-честият случай на употреба е в тези „трансформации в края на конвейера“, в който случай можем просто да направим същата трансформация на всеки документ в резултатите от курсора се върнаха вместо това.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Прочетете файл от mongo shell

  2. как мога да намеря текстово търсене в масив в mongodb

  3. Добре ли е да използвате Object ID на Mongo като негов уникален идентификатор? Ако е така, как мога да го конвертирам в низ и да го търся по низ?

  4. Как да архивирате и възстановите ClusterControl

  5. Вече налични:Напълно хоствани екземпляри на MongoDB на AWS