Need a map reduce function in mongo using php

Need a map reduce function by mongo in php

This my mongo structure

[_id] => MongoId Object (
    [$id] => 4fcf2f2313cfcd2454500000d
)
[id] => 454
[table] => people
[news] => Array (
    [03-06-2012] => 2
    [04-06-2012] => 3
    [05-06-2012] => 5
    [06-06-2012] => 4
)

Here I try to sum the array news with below code,

    $map = new MongoCode('function() { emit(this.news, 1); }');
    $reduce = new MongoCode('function(previous, current) {
                    var count = 0;
                    for (index in current) {
                        count = count + current[index];
                    }
                    return count;
                }');

    $sales = $db->command(array(
        'mapreduce' => 'mycollection',
        'map' => $map,
        'reduce' => $reduce,
        'query' => array('table' => 'people'),
        'out'  => 'news'
    ));

    //pr($sales);exit;

    $users = $db->selectCollection($sales['result'])->find();

    foreach ($users as $user) {
        //echo "{$user['_id']} had {$user['value']} sale(s).\n";
        pr($user);
    }

When pr($user)

Array
(
    [_id] => Array
    (
        [04-06-2012] => 0
        [08-06-2012] => 2
        [11-06-2012] => 6
    )

    [value] => 39540
)

Where I expected a value will be 8 instead of 39540.

How I can correct this function and how to the add a field sum as array sum of 'news' to original collection(mycollection) ?

I am not familar with map reduce functions in mongo.

Answers


When calling emit(), the first parameter is the key you'll be reducing on (or grouping, for this example). The second parameter is the value being emitted for that key, which can be anything. For your example, you probably mean to emit the sum of all values in the news field, using the document's ID as your key:

var map = function() {
    var total = 0;
    for (count in this.news) {
        total += count;
    }
    emit(this._id, total);
}

In this case, a placeholder reduce function can be used (since each emitted key will be unique, there's very little reduction to be done):

var reduce = function(key, values) {
    var total = 0;
    values.forEach(function(v) { total += v; });
    return total;
}

However, as I mentioned in the Google Group post, you may be better off doing this with pure PHP:

$cursor = $collection->find(array(), array('news' => 1));
$cursor->snapshot();

foreach ($cursor as $document) {
    $collection->update(
        array('_id' => $document['_id']),
        array('$set' => array('sum' => array_sum($document['news']))),
        array('multiple' => false)
    );
}

With map/reduce, you'd still have to examine its results and update your records. This would avoid the need to execute JavaScript through Mongo, and should be more performant. And if you can utilize $inc to update the sums as the news field is modified on a per-document basis, that will be even better. The above snippet would still be useful for initializing sum fields across the collection, or correcting any drift if things get out of sync with per-document increments.

Note: see snapshot() in the documentation for the reasoning behind that method call in the example above.


While jmikola's answer gives me wright track to deal with mongo map reduce functions.

I am adding this answer in order to help future visitors.

The following map-reduce function works perfectly to my requirement. This will sum all values in the news field to new collection called news created in command by adding ("out" => "news").

Map-Reduce Function

$map = new MongoCode('function() {
            var total = 0;
            for (count in this.news) {
            total +=  this.news[count];
            }
            emit(this._id, {id: this.id, total: total});
        }');
$reduce = new MongoCode('function(key, values) {
            var result = {id: null, total: 0};
            values.forEach(function(v) {
            result.id = v.id;
            result.total = v.total;
             });
            return result;
        }');

$sales = $db->command(array(
    'mapreduce' => 'mycollection', // collection name
    'map' => $map,
    'reduce' => $reduce,
    'query' => array('table' => 'people'),
    "out" => "news" // new collection name
));

The result will be news collection with sum as total and id of actual document

Output

[_id] => MongoId Object (
    [$id] => 4fd8993a13cfcd4e42000000
)
[value] => Array (
    [id] => 454
    [total] => 14
)

Need Your Help

Send JSON in Post with robospice google http client

android json post robospice google-http-client

I have a problem with creating post requests and send json with Robospice google http java client. My problem is, that the server receives an empty request data. (Nothing in postData)

Localize NSDateFormatter which has a custom string?

ios objective-c localization nsdateformatter

I want to localize NSDateFomatter, which has 'at' word in between date and time. Any idea how to do it?