Для упрощения, я предполагаю, что у вас есть документы вида:
{category: <int>, score: <int>}
Я создал 1000 документов, охватывающих 100 категорий:
for (var i=0; i<1000; i++) {
db.foo.save({
category: parseInt(Math.random() * 100),
score: parseInt(Math.random() * 100)
});
}
Наш картограф довольно прост, просто выдает категорию в качестве ключа и объект, содержащий массив баллов в качестве значения:
mapper = function () {
emit(this.category, {top:[this.score]});
}
Редуктор MongoDB не может возвращать массив, и выход редуктора должен быть того же типа, что и значения, которые мы emit
, поэтому мы должны заключить его в объект. Нам нужен массив баллов, так как это позволит нашему редуктору вычислить верхние 3 балла:
reducer = function (key, values) {
var scores = [];
values.forEach(
function (obj) {
obj.top.forEach(
function (score) {
scores[scores.length] = score;
});
});
scores.sort();
scores.reverse();
return {top:scores.slice(0, 3)};
}
Наконец, вызовите карту-уменьшить:
db.foo.mapReduce(mapper, reducer, "top_foos");
Теперь у нас есть коллекция, содержащая по одному документу на категорию, и первые 3 балла по всем документам из foo
в этой категории:
{ "_id" : 0, "value" : { "top" : [ 93, 89, 86 ] } }
{ "_id" : 1, "value" : { "top" : [ 82, 65, 6 ] } }
(Ваши точные значения могут отличаться, если вы использовали тот же генератор данных Math.random()
, что и у меня выше)
Теперь вы можете использовать это, чтобы запросить foo
для фактических документов, имеющих эти лучшие оценки:
function find_top_scores(categories) {
var query = [];
db.top_foos.find({_id:{$in:categories}}).forEach(
function (topscores) {
query[query.length] = {
category:topscores._id,
score:{$in:topscores.value.top}
};
});
return db.foo.find({$or:query});
}
Этот код не будет обрабатывать связи, точнее, если связи существуют, более 3 документов могут быть возвращены в конечном курсоре, созданном find_top_scores
.
Решение с использованием group
было бы несколько похоже, хотя редуктор должен будет рассматривать только два документа одновременно, а не массив оценок для ключа.