Агрегация MapReduce на основе атрибутов, содержащихся за пределами документа

Скажем, у меня есть набор «действий», каждое из которых имеет имя, стоимость и местоположение:

{_id : 1 , name: 'swimming', cost: '3.40', location: 'kirkstall'}
{_id : 2 , name: 'cinema', cost: '6.50', location: 'hyde park'}
{_id : 3 , name: 'gig', cost: '10.00', location: 'hyde park'}

У меня также есть коллекция people, которая записывает для каждого действия, сколько раз они планируют выполнять каждое в год:

{_id : 1 , name: 'russell', activities : { {1 : 9} , {2 : 4} , {3 : 21} }}

Я не хочу денормализовать атрибуты действий, помещая их в коллекцию person по ряду причин.

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

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

Однако теперь я хочу сделать MapReduce, чтобы узнать, сколько всего мероприятий запланировано всеми людьми, сгруппированными по местоположению.

Это означает, что во время MapReduce для коллекции людей мне нужно знать местоположение запланированных ими действий. Может ли кто-нибудь придумать хороший способ сделать это?

Моя лучшая попытка на данный момент (что довольно глупо) — это создание хранимой функции javascript, которая принимает массив идентификаторов действий, запрашивает набор действий и возвращает карту идентификаторов действий в местоположение. Затем я бы вставил это в функцию map и поискал в ней места. Это было бы довольно глупо, как я уже сказал, поскольку один и тот же запрос в коллекции activities будет выполняться один раз для каждого элемента в коллекции people.


person Russell    schedule 07.09.2011    source источник


Ответы (1)


Я сделал это, обернув MapReduce в какой-то сохраненный javascript.

function (query) {

  var one = db.people.findOne(query);
  var activity_ids = [];
  for (var k in one.activities){
    activity_ids.push(parseInt(k));
  }

  var activity_location_map = {};
  db.activities.find({id : {$in : activity_ids}}).forEach(function(a){
    activity_location_map[a.id] = a.location;
  });


  return db.people.mapReduce(
    function map(){
      for (var k in this.activities){
        emit({location : activity_location_map[k]} , { total: this.activities[k] });
        emit({location: activity_location_map[k]} , { total: this.activities[k] });
      }
    },
    function reduce(key, values){
      var reduced = {total: 0};
      values.forEach(function(value){
        reduced.total += value.total;
      });

      return reduced;
    },
    {out : {inline: true}, scope : { activity_location_map : activity_location_map }}
  ).results;
}

Раздражает и грязно, но это работает, и я не могу придумать ничего лучше.

person Russell    schedule 07.09.2011