В Монго, как я могу использовать карту Reduce, чтобы получить группу по порядку самых последних - PullRequest
4 голосов
/ 03 сентября 2011

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

Я предполагаю, что я также могу использовать групповую функцию, но мне было любопытно, поскольку они утверждают, что в защищенных средах нельзя использовать group (). Тем не менее, мне действительно интересно посмотреть на пример group ().

1 Ответ

7 голосов
/ 03 сентября 2011

Для упрощения, я предполагаю, что у вас есть документы вида:

{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 было бы несколько похоже, хотя редуктор должен будет рассматривать только два документа одновременно, а не массив оценок для ключа.

...