Mongo mapreduce — используйте $dateToString на этапе сопоставления

У меня есть простая коллекция элементов со следующей структурой:

{
   _id: MongoId
   pvs: int
   day: IsoDate()
   uid: int
}

Я хотел бы использовать MapReduce для подсчета просмотров страниц для данного пользователя, сгруппированных по определенному диапазону дат (день/неделя/месяц, дата format совместима).

Что я застрял, так это то, как переформатировать IsoDate в функции карты, используя $dateToString перед отправкой, чтобы она выдавала нужный мне формат, например %Y-%m-%d или %Y-%m или %Y-%m-%U. Когда я его вызываю, я получаю не переформатированную дату, а объект с полями format и date.

Пример:

function(){
    emit(
        {'$dateToString': {'format': "%Y-%m-%d", 'date': this.day}}, 
        this.pvs
    )}

вернется

{
    "pvs" : 5
    "$dateToString" : {
        "format" : "%Y-%m-%d",
        "date" : ISODate("2016-07-13T08:27:29.000Z")
    }
}

Вместо этого я хочу вернуть это:

{
    "pvs": 5,
    "day": "2016-07-13"
}

person ex3v    schedule 01.09.2016    source источник
comment
Почему вы не можете использовать структуру агрегации, где легко доступен оператор $dateToString, а также эффективная агрегация по сравнению с mapReduce?   -  person chridam    schedule 01.09.2016
comment
@chridam, к сожалению, я не могу - я использую doctrine mongo odm, что позволяет мне звонить только mapReduce (я знаю, глупо). А еще я хотел бы решить этот вопрос, просто чтобы чему-то научиться.   -  person ex3v    schedule 01.09.2016


Ответы (1)


Если вы используете mapReduce, вам нужно будет создать собственную пользовательскую функцию, которая форматирует дату и вызывает ее в вашей функции карты:

dateToString = function(date){
    return date.getFullYear() + '-' (date.getMonth() + 1) + '-' + date.getDate();
}

map = function() {
    emit(dateToString(this.day), this.pvs);
}

Лучше с платформой агрегации, которая работает «внутри» MongoDB в своем коде C++, поэтому более эффективна, чем mapReduce, которая работает в среде V8/spidermonkey (в зависимости от вашей версии) в связанной консоли JS:

db.collectionName.aggregate([
    { "$match": { "uid": userId } },
    { 
        "$project": {
            "formattedDate": { 
                "$dateToString": { "format": "%Y-%m-%d", "date": "$day" } 
            },
            "pvs": 1
        }
    },
    {
         "$group": {
             "_id": "$formattedDate",
             "pvs": { "$sum": "$pvs" }
         }
    }
])

что в доктрине mongo odm вы можете запустить свой конвейер, используя функцию command как:

$connection = $this->get('doctrine_mongodb')->getConnection();
$mongo = $connection->getMongo();
if (!$mongo) {
    $connection->connect();
    $mongo = $connection->getMongo();
}
$db = $mongo->selectDB('test_database');
$aggregate_results = $db ->command(array( 
    "aggregate" => "collectionName",
    "pipeline" => array( 
        array("$match" => array("uid"=>  userId )),
        array( 
            "$project" => array(
                "formattedDate" => array( 
                    "$dateToString" => array("format" => "%Y-%m-%d", "date"=>  "$day") 
                ),
                "pvs" =>  1
            )
        ),
        array(
             "$group" => array(
                 "_id" => "$formattedDate",
                 "pvs" => array("$sum" => "$pvs")
             )
        )
    )
));
person chridam    schedule 01.09.2016
comment
Спасибо! На самом деле мне грустно, что что-то простое, например формат даты, недоступно прямо в mapReduce, но вы предоставили рабочее решение :) - person ex3v; 01.09.2016