Я тут щас aggregation и mapReduce изучаю. Заодно открыл для себя $where.
$where - позволяет стрингой передать сроку кода, например:
Пример player'а:
{
_id: 42,
prizes: [
{ created: 123, type: 'foo' },
{ created: 234, type: 'bar' }
]
}
И вот такой query:
players.count({ $where: 'this.prizes.length >= 20' }, function(err, count) {
if(!err) {
// count - сколько игроков имеет призов больше чем 20
} else {
...
}
});
Если в бд вбить:
db.players.find({ $where: 'this.prizes.length >= 20' }).explain()
То выдаст факт того что тут ничего естественно не индексируется, индекс тоже игнорируется.
Следственно например для collection'а с 2800 записями, у меня затрачивается 72мс (на простом Mac'е), что ужасно долго для таких запросов.
Следственно в реальном мире лучше кешировать длину массива рядом с массивом.
Но всё же, если у тебя например запрос на 32 записи (например), то такой код - вполне приемлем. Нужно исследовать как там с блоком всего процесса (блокирует ли другие запросы и т.п.).
aggregation - это интерестный монстрик, по получению всяких данные и более сложных манипуляций над документами.
Например у меня есть записи игроков, и у каждого игрока есть любимая команда. Мне нужно получить список всех команд и сколько игроков её указали как любимую:
players.collection().aggregate([
{
$match: {
favourite: { $exists: true } // ищем всех игроков у которых указан 'favourite'
}
}, {
$group: { // групируем все записи
_id: "$favourite", // по favourite полю
total: { $sum: 1 } // и прибавляем 1 к total
}
}, {
$sort: { // потом сортируем по убыванию total
total: -1
}
}
], function(err, data) {
if (!err) {
//
} else {
...
}
});
favourite - это id команды простое число.
Всего там 20 команд.
В результате получу массив с до 20 записями такого вида:
{
_id : 685,
total : 813
}
Для сборов статистики - самое то. Естетсвенно это не супер шустро такие статистически сборы делать. Но в aggregate есть не мало разных операторов, которые используют индексацию, что ускоряет весьма не плохо. Для не больших запросов (до 64 записей), это весьма приемлемый подход. Естественно всегда можно закешить результаты.
mapReduce - это ещё следующий шаг. Когда aggregation предоставляет операторы, и там важна их поочерёдность и т.п., то mapReduce принимает лишь 2 функции и query фильтр.
Первая функция обрабатывает каждый документ (удовлетворяющий query) и если нужно вызывает emit, что передаёт уже обработанные данные (можно и не обработанные) в reduce функцию, где уже дальше мы имеем дело с групированными данными.
Примеров много можно привезти, в моём случае, нужно было узнать сколько призов в среднем выйграл каждый игрок. Есть 2800 записей. И нужно получить общее арифметическое колличества призов:
players.collection().mapReduce(function() {
emit('stats', this.prizes.length); // map - для всех запускаем reduce с групированием (_id будет 'stats')
}, function(key, values) { // reduce
var total = 0; // всего призов
var i = values.length;
while(i--) { // для каждой длины массива призов
total += values[i]; // складываем
}
return total / values.length; // и делим на количество значений
}, {
query: { confirmed: true }, // только игрокам кто подтвердил свой аккаунт
out: { inline: 1 }
}, function(err, data) {
if (!err) {
// data[0].value - общее арифметическое длин массива призов у каждого игрока
} else {
...
}
});
На самом деле это можно было сделать и aggregator'ом, но я хотел попробовать mapReduce.
Снова, для тысячей записей - это не шустро, но для статистики - самое то.