MongoDB Map-Reduce

MapReduce is a data processing technique in MongoDB used to process large amounts of data and perform calculations such as sum, count, average, grouping, etc.

MongoDB Map-Reduce consists of two main functions:

  • Map function → reads each document and emits a key-value pair.
  • Reduce function → combines the values having the same key.
  • out → specifies where the result should be stored.

Basic syntax:

db.collection.mapReduce(
    mapFunction,
    reduceFunction,
    { out: "resultCollection" }
)

Example 1: Find Sum of Marks Grouped by Age

Suppose we have the following student collection:

use student

db.studentdata.insertMany([
    {name: "Ram", marks: 40, age: 20},
    {name: "Shyam", marks: 50, age: 20},
    {name: "Swati", marks: 60, age: 18},
    {name: "Rahul", marks: 60, age: 21},
    {name: "Riya", marks: 70, age: 21}
])

Display the data:

db.studentdata.find()

Map function

var mapFunction = function() {
    emit(this.age, this.marks);
};

Here:

  • this.age → key
  • this.marks → value

So the emitted data is approximately:

20 → 40
20 → 50
18 → 60
21 → 60
21 → 70

Reduce function

var reduceFunction = function(key, values) {
    return Array.sum(values);
};

Execute MapReduce

db.studentdata.mapReduce(
    mapFunction,
    reduceFunction,
    {out: "Result_mapreduce"}
)

Display the result:

db.Result_mapreduce.find()

Output

{ "_id" : 18, "value" : 60 }
{ "_id" : 20, "value" : 90 }
{ "_id" : 21, "value" : 130 }

Explanation

For age 20:

40 + 50 = 90

For age 21:

60 + 70 = 130

Therefore, MapReduce groups students by age and calculates the total marks for each age group.


Example 2: Calculate Average Marks Grouped by Age

Using the same collection:

db.studentdata.find()

Map function

var mapFunction = function() {
    emit(this.age, this.marks);
};

Reduce function

For an average, a simple Array.avg(values) should not be used here. A reliable MapReduce approach is to emit both the sum and count.

var mapFunction = function() {
    emit(this.age, {
        sum: this.marks,
        count: 1
    });
};

Reduce:

var reduceFunction = function(key, values) {
    var result = {
        sum: 0,
        count: 0
    };

    values.forEach(function(value) {
        result.sum += value.sum;
        result.count += value.count;
    });

    return result;
};

Run:

db.studentdata.mapReduce(
    mapFunction,
    reduceFunction,
    {out: "AverageMarks"}
)

Then:

db.AverageMarks.find()

The result contains the sum and count for each age. The average is:

Average = sum / count

For example, age 20:

Sum = 90
Count = 2

Average = 90 / 2 = 45

Example 3: Count Books Written by Each Author

Consider a books collection:

use books

db.books.insertMany([
    {
        title: "MongoDB: The Definitive Guide",
        authors: [
            {firstName: "BRIJESH", lastName: "PATEL"}
        ],
        price: 32.99
    },
    {
        title: "MongoDB in Action",
        authors: [
            {firstName: "JAYESH", lastName: "PATEL"}
        ],
        price: 30.83
    },
    {
        title: "MongoDB in Action 2",
        authors: [
            {firstName: "JIGNESH", lastName: "THANKI"}
        ],
        price: 35.30
    },
    {
        title: "Scaling MongoDB",
        authors: [
            {firstName: "NIKHIL", lastName: "DAVE"}
        ],
        price: 25.30
    }
])

Map function

var mapFunction = function() {
    for (var i = 0; i < this.authors.length; i++) {
        var author = this.authors[i];

        emit(
            author.firstName + " " + author.lastName,
            1
        );
    }
};

Each author emits:

BRIJESH PATEL → 1
JAYESH PATEL → 1
JIGNESH THANKI → 1
NIKHIL DAVE → 1

Reduce function

var reduceFunction = function(author, counts) {
    var total = 0;

    for (var i = 0; i < counts.length; i++) {
        total += counts[i];
    }

    return total;
};

Execute

db.books.mapReduce(
    mapFunction,
    reduceFunction,
    {out: {inline: 1}}
)

Output

BRIJESH PATEL    → 1
JAYESH PATEL     → 1
JIGNESH THANKI   → 1
NIKHIL DAVE      → 1

If an author has written multiple books, the reduce function will add those 1 values and give the total number of books written by that author.


Example 4: Sum of Rank Grouped by Age

Suppose:

use employee

db.employee.insertMany([
    {id: 1, rank: 10, age: 80},
    {id: 2, rank: 20, age: 80},
    {id: 3, rank: 30, age: 90},
    {id: 4, rank: 40, age: 90},
    {id: 5, rank: 50, age: 70}
])

Map

var mapFunction = function() {
    emit(this.age, this.rank);
};

Reduce

var reduceFunction = function(key, values) {
    return Array.sum(values);
};

Execute

db.employee.mapReduce(
    mapFunction,
    reduceFunction,
    {out: "RankResult"}
)

Display:

db.RankResult.find()

Output

{ "_id" : 70, "value" : 50 }
{ "_id" : 80, "value" : 30 }
{ "_id" : 90, "value" : 70 }

Because:

Age 70 → 50
Age 80 → 10 + 20 = 30
Age 90 → 30 + 40 = 70

Important correction in your original example

You wrote:

var map=function(){ emit(this.age,this.rank)};
var reduce=function(age,rank){ return Array.avg(rank);};

The problem is that Array.avg() is not the appropriate MapReduce reducer for calculating an average.

Also, if rank contains values such as:

"A", "A", "B", "B", "C"

you cannot calculate their numerical average because they are strings.

For example:

{id: 1, rank: "A", age: 80}

Here "A" is text, not a number.

If you want to calculate an average, use a numeric field such as:

{age: 20, marks: 40}

and calculate average using sum + count.

MapReduce flow

Documents
    ↓
Map Function
    ↓
Key-Value pairs
    ↓
Group by Key
    ↓
Reduce Function
    ↓
Result Collection

Note: MapReduce is a legacy MongoDB feature; for new MongoDB applications, the aggregation pipeline is generally preferred.

By admin