четверг, 3 июля 2014 г.

MongoDB Aggregation

С помощью этого функционала мы можем групировать по неким полям наши результаты запросов, при этом проводить некие исчисления над полями одной групы для получения общего результата для группы; преобразовывать; фильтровать; сортировать; обрезать; а также строить пайпланы для обработки уже обработанных результатов.

Aggregation Pipeline

Collection -> $project -> $match -> $group -> $sort -> Result

$project   - reshape     - 1 : 1
$match    - filter          - n : 1
$group    - aggregate  - n : 1
$sort        - sort           - 1 : 1
$skip       - skips         - n : 1
$limit      - limits         - n : 1
$unwind - normalize  - 1 : n
$out        - output       - 1 : 1  (направить результат в другую колекции из базы)

$redact    - на сколько я понял, какие-то ограничения по пользователю, то есть эта директива позволяет проверять поля на значения и включать их или нет в ответ, а к пользователю приложение привязывает некое значение полей и на основании этих полей происходит фильтрация.
$geoNear - поиск относительно места расположения, в результате возвращается сначала самое близкое место и в конце самое отдаленное.




Как работает агрегация:
db.products.aggregate([
  {$group:
    {
      _id: "$oneOfFieldsFromProductsByWhichWeAreGrouping",
      weCreateThisFiledInResultByNextLogic: {$sum: 1} // this is as counting quantity of documents which is in each group
    }
  }
])

Групировака по нескольким полям(в ввиде сложенной айдишки для документов результата):
db.products.aggregate([
  {$group:
    {
      _id: {
        maker: "$manufacturer",
        category: "$category"
      },
      num_products: {$sum: 1} 
    }
  }
])
Кстати мы так можем и создавать документы в колекциях, а не позволять генерить монго автоматические ключи.

И чтобы суммировать  какое-то поле у всех документах колекции или стадии агрегейшин пайплайн, мы используем тоже группировку, но группируем по null
db.products.aggregate([
  {$group:
    {
      _id: null,
      toBuyEverithingInShopYouNeed: {$sum: "$price"} 
    }
  }
])

Aggregation Expressions for Grouping Stage

$sumМожно каунтить docs или сумировать значения указанного поляdb.zips.aggregate([{$group: {_id: "$state", population: {$sum: "$pop"}}}])
$avg
$min
$max
$pushСобирать указанное поле в массив для групы
$addToSetСобирать указанное поле в множество(в виде моссива без дубликатов) для групыdb.zips.aggregate([{$group: {_id: "$city", postal_codes: {$addToSet: "$_id"}}}])

Часть результата:
{
"_id" : "CENTREVILLE",
"postal_codes" : [
"22020",
"49032",
"39631",
"21617",
"35042"
]

},
$firstДолжно работать вместе с $sort потому что в противном случае не имеет смысла
$lastДолжно работать вместе с $sort потому что в противном случае не имеет смысла

Групировка групировки

db.grades.aggregate([
  {$group: {_id:{class_id: "$class_id", student_id: "$student_id"}, 'average':{$avg: "$score"}}},
  {$group: {_id:"$_id.class_id", "average":{$avg: "$average"}}}
])

Преобразование

db.zips.aggregate([
  {$project: { 
     _id: 0,//don't include in result 
     city: {$toLower: "$city"}, 
     pop: 1,//set as it is 
     state: 1,//set as it is 
     zip: "$_id"
  }}
])
Результат:
{
 "city" : "acmar",
 "pop" : 6055,
 "state" : "AL",
 "zip" : "35004"
}

Фильтрация

db.zips.aggregate([
  {$match:
    {pop: {$gt: 100000}
  },
  {$group: {...}}
])

Сортировка

db.zips.aggregate([
  {$match:
    {state: "NY"
  },
  {$group: 
   {
      _id: "$city",
      population: {$sum: "$pop"}
   }
  },
  {$project:
   {
      _id: 0,
      city: "$_id",
      population: 1
   }
  },
  {$sort:
   {
      population: -1
   }
  }
])

Пропуск и ограничение

Будет пустым если в пайплайне нет перед ним сортировки.
db.zips.aggregate([
    {$match:
     {
  state:"NY"
     }
    },
    {$group:
     {
  _id: "$city",
  population: {$sum:"$pop"},
     }
    },
    {$project:
     {
  _id: 0,
  city: "$_id",
  population: 1,
     }
    },
    {$sort:
     {
  population:-1
     }
    },
    {$limit: 5},
    {$skip: 10} 
])
Но предыдущий пример также будет пустым, потому что мы сначала отрезаем 5 с начала, а потом из этих 5-ти пытаемся пропустить 10 - как итог получаем сдвиг за пределы массива результата.

Первый и последний

Будет пустым если в пайплайне нет перед ним сортировки.
db.fun.aggregate([
    {$match:{a:0}},
    {$sort:{c:-1}}, 
    {$group:{_id:"$a", c:{$first:"$c"}}}
])

Дробление-нормализация

Имеем документ в posts:
{
  id: ...,
  author: "Some Name",
  tags: ["tag1", "tag2", "tag3"],
  ...
}

db.posts.aggregate([
    {$unwind: "$tags"},
    {$group:
     {
  _id: "$tags",
  count: {$sum:1},
     }
    },
    ... 
])

Теперь каждый документ с массивом тегов превращется в N документов(где N размер массива). Все поля повторяются, а tags становится полем со значением из елемента массива.

Нужно изучать отдельной темой:
http://docs.mongodb.org/manual/reference/sql-aggregation-comparison/

 Ограничения в Aggregation

1. Мы имеем ограничение 100 Мб оперируемых данных в pipeline stages. Эта проблема решается allowDiskUse директивой, но у нас падает продуктивность/запроса.

2. Если мы хотим вернуть результат одним документом, то всегда нужно помнить о ограничении в 16 Мб на такой документ, чтобы не сталкиваться с таким ограничением - пользуемся cursor.

3. Если вызывать на шардированной системе group by или sort, у нас общий результат со всех шардов будет возвращен на первый шард(праймери шард) и там будет пытаться в его оперативки сделаться групировка и/или сортировка. Это конечно же может завалить этот шард, эта проблема решается например Hadoop-ом.


Комментариев нет:

Отправить комментарий