mathieug
mathieug

Reputation: 901

MongoDB convert mapReduce to aggregation

I am trying to convert a mapReduce into an aggregation.

I'm stuck because I have multiple calls to emit() in the map() function. I don't see how I can do it with $group.

Here's the mapReduce:

function map() {
  const dateHour = this.createdAt.toISOString().substr(0, 13);
  const value = {
    orders: 1,
    amount: this.amount
  };

  emit({
    date: dateHour,
    type: 'global',
    granularity: 'hour'
  }, value);

  if (this.companyId) {
    emit({
      date: dateHour,
      type: 'company',
      companyId: this.companyId,
      granularity: 'hour'
    }, value);
  }

  if (this.employeeId) {
    emit({
      date: dateHour,
      type: 'employee',
      employeeId: this.employeeId,
      granularity: 'hour'
    }, value);
  }

  emit({
    date: dateHour,
    type: 'kitchen',
    kitchenId: this.kitchenId,
    granularity: 'hour'
  }, value);
}

function reduce(key, values) {
  return values.reduce((accumulator, value) => ({
    orders: accumulator.orders + value.orders,
    amount: accumulator.amount + value.amount,
  }), {
    orders: 0,
    amount: 0
  });
}

Sample input:

{ 
    "_id" : ObjectId("586f5fc7f3f3bf001178e2bf"), 
    "deliverySlotStart" : ISODate("2017-01-06T11:00:00.000+0000"), 
    "amount" : 11.0
    "kitchenId" : ObjectId("5858154c3aa80f1120c78c08"), 
    "createdAt" : ISODate("2017-01-06T09:13:43.354+0000"), 
}
{ 
    "_id" : ObjectId("586f657ef3f3bf001178e2c0"), 
    "deliverySlotStart" : ISODate("2017-01-06T11:00:00.000+0000"), 
    "amount" : 11.0
    "kitchenId" : ObjectId("5858154c3aa80f1120c78c08"), 
    "createdAt" : ISODate("2017-01-06T09:38:06.174+0000"), 
    "employeeId" : ObjectId("58948c82f1efa800115a484e"), 
    "companyId" : ObjectId("5891d7dd50b5e76b7733f27b")
}

Output:

{ 
    "_id" : {
        "date" : "2017-01-06T09", 
        "type" : "company", 
        "companyId" : ObjectId("5891d7dd50b5e76b7733f27b"), 
        "granularity" : "hour"
    }, 
    "value" : {
        "orders" : 1.0, 
        "amount" : 11.0
    }
}
{ 
    "_id" : {
        "date" : "2017-01-06T09", 
        "type" : "employee", 
        "employeeId" : ObjectId("58948c82f1efa800115a484e"), 
        "granularity" : "hour"
    }, 
    "value" : {
        "orders" : 1.0, 
        "amount" : 11.0
    }
}
{ 
    "_id" : {
        "date" : "2017-01-06T09", 
        "type" : "global", 
        "granularity" : "hour"
    }, 
    "value" : {
        "orders" : 2.0, 
        "amount" : 22.0
    }
}
{ 
    "_id" : {
        "date" : "2017-01-06T09", 
        "type" : "kitchen", 
        "kitchenId" : ObjectId("5858154c3aa80f1120c78c08"), 
        "granularity" : "hour"
    }, 
    "value" : {
        "orders" : 2.0, 
        "amount" : 22.0
    }
}

Upvotes: 2

Views: 294

Answers (1)

Asya Kamsky
Asya Kamsky

Reputation: 42342

There are two ways to do what you are trying to do in aggregation in 3.4. One approach is to use the $facet stage which allows you to split the pipeline into multiple parallel stages, the other way by creating an array field and then unwinding it, so that you can group by various combination of types and values.

Using $facet:

db.coll.aggregate([
    {$addFields:{dh:{$dateToString:{format:"%Y-%m-%dT%H",date:"$createdAt"}}}}, 
    {$facet:{
       global:[
          {$group:{_id:{dateHour:"$dh", type: "global"},
                   orders:{$sum:1},
                   amount:{$sum:"$amount"}}} ],
       company:[
          {$group:{_id:{dateHour:"$dh", type: "company"},
                   orders:{$sum:{$cond:[{$eq:[{$ifNull:["$companyId",null]},null]},0,1]}},
                   amount:{$sum:{$cond:[{$eq:[{$ifNull:["$companyId",null]},null]},0,"$amount"]}} }} ],
       employee:[
          {$group:{_id:{dateHour:"$dh", type: "employee"}, 
                   orders:{$sum:{$cond:[{$eq:[{$ifNull:["$employeeId",null]},null]},0,1]}},
                   amount:{$sum:{$cond:[{$eq:[{$ifNull:["$employeeId",null]},null]},0,"$amount"]}} }} ], 
       kitchen:[
          {$group:{_id:{dateHour:"$dh", type: "kitchen"}, 
                   orders:{$sum:{$cond:[{$eq:[{$ifNull:["$kitchenId",null]},null]},0,1]}},  
                   amount:{$sum:{$cond:[{$eq:[{$ifNull:["$kitchenId",null]},null]},0,"$amount"]}} }}  ]
    }}
])

Generating array:

db.coll.aggregate([
   {$addFields:{
     a:{$let:{
        vars: {dh:{$dateToString:{format:"%Y-%m-%dT%H",date:"$createdAt"}}},
        in: { 
             dateHour:"$$dh", 
             t:[
                {type:"global"},
                {$cond:[{$eq:[{$ifNull:["$companyId",null]},null]}, "", {type:"company", companyId:"$companyId"}]},
                {$cond:[{$eq:[{$ifNull:["$employeeId",null]},null]}, "",  {type:"employee", employeeId:"$employeeId"}]},
                {$cond:[{$eq:[{$ifNull:["$kitchenId",null]},null]}, "", {type:"kitchen", kitchenId:"$kitchenId"}]} 
             ]
     }}}}}, 
   {$unwind:"$a.t"},
   {$match:{"a.t":{$ne:""}}},
   {$group:{
      _id:{date:"$a.dateHour", type:"$a.t.type", companyId:"$a.t.companyId", kitchenId:"$a.t.kitchenId", employeeId:"$a.t.employeeId", granularity:"hour"}, 
      orders:{$sum:1}, 
      amount:{$sum:"$amount"}}},
   {$sort:{"_id.type":1}}
])

Upvotes: 3

Related Questions