Поддокумент MapReduce - PullRequest
2 голосов
/ 24 января 2012

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

Вот пример документа:

{
  "_id" : new BinData(3, "wbbS0lRI0ESx5DyStKq9pA=="),
  "MemberId" : null,
  "NewsletterId" : 4,
  "NewsletterTypeId" : null,
  "Contents" : "[message goes here]",
  "History" : [{
      "EmailActionType" : "spam",
      "DateAdded" : new Date("Sat, 10 Dec 2011 04:17:26 GMT -08:00")
    }, {
      "EmailActionType" : "processed",
      "DateAdded" : new Date("Sun, 11 Dec 2011 04:17:26 GMT -08:00")
    }, {
      "EmailActionType" : "deffered",
      "DateAdded" : new Date("Mon, 12 Dec 2011 04:17:26 GMT -08:00")
    }],
  "DateAdded" : new Date("Mon, 01 Jan 0001 00:00:00 GMT -08:00")
}

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

date: "20111210", spam: 1, processed: 0, deffered: 0
date: "20111211", spam: 0, processed: 1, deffered: 0
date: "20111212", spam: 0, processed: 0, deffered: 1

Вот что у меня сейчас есть:

db.runCommand({ mapreduce: Email, 
 map : function Map() {
    var key   = this.NewsletterId;
    emit(
            key,
            { "history" : this.History }
        ); 
}
 reduce : function Reduce(key, history) {
    var from = new Date (2011, 1, 1, 0, 0, 0, 0);
    var to = new Date (2013, 05, 15, 23, 59, 59, 0);

    // \/ determine # days in the date range \/
    var ONE_DAY = 1000 * 60 * 60 * 24; // The number of milliseconds in one day
    var from_ms = from.getTime(); // Convert both date1 to milliseconds
    var to_ms = to.getTime(); // Convert both date1 to milliseconds

    var difference_ms = Math.abs(from_ms - to_ms); // Calculate the difference in milliseconds 
    var numDays = Math.round(difference_ms/ONE_DAY); // Convert back to days and return
    // /\ determine # days between the two days  /\

    var results = new Array(numDays); //array where we will store the results. We will have an entry for each day in the date range.

    //initialize array that will contain our results for each type of emailActivity
    for(var i=0; i < numDays; i++){
        results[i] = {
            numSpam: 0,
            numProcessed: 0,
            numDeffered: 0
        }
    }

    //traverse the history records and count each type of event
    for (var i = 0; i < history.length; i++){
        var to_ms2 = history[i].DateAdded.getTime(); // Convert both date1 to milliseconds

        var difference_ms2 = Math.abs(from_ms - to_ms2); // Calculate the difference in milliseconds 
        var resultsIndex = Math.round(difference_ms2/ONE_DAY); //determine which row in the results array this date corresponds to

        switch(history[i].EmailActionType)
        {
            case 'spam':
               results[resultsIndex].numSpam = ++results[resultsIndex].numSpam;
               break;
            case 'processed':
              results[resultsIndex].numProcessed =  ++results[resultsIndex].numProcessed;
               break;
            case 'deffered':
               results[resultsIndex].numDeffered = ++results[resultsIndex].numDeffered;
               break;
        }
    }
    return results; 
}
 finalize : function Finalize(key, reduced) {
    return { 
        "numSpam": reduced.numSpam,
        "numProcessed": reduced.numProcessed,
        "numDeffered": reduced.numDeffered, 
    };
}
 out : { inline : 1 }
 });

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

1 Ответ

3 голосов
/ 24 января 2012

Ваша проблема определенно связана с функциями Map / Reduce. Существует разрыв между вашим emit и ожидаемым результатом.

Ваш ожидаемый результат:

date: "20111210", spam: 1, processed: 0, deffered: 0

Map / Reduce всегда выводит в терминах key и value. Таким образом, ваш вывод будет выглядеть так:

_id: "20111220", value: { spam: 1, processed: 0, deferred: 0 }

Вот основная предпосылка. Ваш emit должен выводить данные в правильном формате. Так что если вы emit(key, value), то вы должны иметь:

var key='20111220'
var value={spam:1, processed:0, deferred:0}

В вашем случае, вы излучаете несколько раз за документ, когда просматриваете History. Это нормально.

Функция reduce запускается только при наличии нескольких значений для одной и той же клавиши. Так что, если у вас есть это:

_id: "20111220", value: { spam: 1, processed: 0, deferred: 0 }
_id: "20111220", value: { spam: 1, processed: 2, deferred: 0 }

Тогда reduce соберет их вместе и даст вам следующее:

_id: "20111220", value: { spam: **2**, processed: **2**, deferred: 0 }

Вот быстрый ответ на вопрос:

map = function() {
  for(var i in this.History) {
    var key = get_date(this.History[i].DateAdded);
    var value = {spam: 0, processed: 0, deffered: 0};

    if(this.History[i].EmailActionType == "Spam") { value.spam++; }
    else if(....)
    ...

    emit(key, value);
  }
}

reduce = function(key, values) { 
  // values is an array of these things {spam: 0, processed: 0, deffered: 0}
  var returnValue = { spam: 1, processed: 0, deffered: 0 };
  for(var i in values) {
    returnValue.spam += values[i].spam;
    returnValue.processed += values[i].processed;
    returnValue.deffered += values[i].deffered;
  }
  return returnValue;
}

Просто помните, что структура emit должна соответствовать структуре ваших окончательных значений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...