Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongo Aggregation - Using variables created in $project

Is it possible to use variables defined in the $project phase in that same phase?

For example, I have this aggregation pipeline:

pipeline = [
  { '$match': {} },
  {
    '$group': {
      '_id': '$_id',
      'n': { '$first': 'n' }
    }
  },
  {
    '$project': {
      'name': 1,
      'n': 1,
      'revenue': { '$multiply': ['$n', 2] },
      'cost': { '$multiply': ['$revenue', 0.25] }
    }
  }
]

I'd like to use the $revenue variable (that I defined in $project) in the same $project stage to compute the value of cost, but this does not work.

Is there any way I can do this easily and efficiently? I thought about doing several projections but I would need to project many variables (~25 variables) every time I need to compute one additional variable, and I have a bunch of variables to compute that are depending on each other (~5 variables), and my code would probably look bad since there would be a lot of projecting of the same variables. How should I go about this?

like image 228
Marc Avatar asked Dec 17 '13 16:12

Marc


3 Answers

No, but you can nest projection operators as a work-around:

pipeline = [{   
    '$match': {}
},
{
    '$group': {
        '_id': '$_id',
        'n': {'$first': 'n'}
},
{
    '$project': {
        'name': 1,
        'n': 1,
        'revenue': {'$multiply': ['$n', 2]},
        'cost': {'$multiply': [{'$multiply': ['$n', 2]}, 0.25]}
    }
}]

Which in this case you could simplify to:

pipeline = [{   
    '$match': {}
},
{
    '$group': {
        '_id': '$_id',
        'n': {'$first': 'n'}
},
{
    '$project': {
        'name': 1,
        'n': 1,
        'revenue': {'$multiply': ['$n', 2]},
        'cost': {'$multiply': ['$n', 2, 0.25]}
    }
}]
like image 121
JohnnyHK Avatar answered Oct 25 '22 06:10

JohnnyHK


Just landed here while searching for the same issue. My current solution is adding more $project stages and always take all other fields. For your example it would look like this:

pipeline = [{   
    '$match': {}
},
{
    '$group': {
        '_id': '$_id',
        'n': {'$first': 'n'}
},
{
    '$project': {
        'name': 1,
        'n': 1,
        'revenue': {'$multiply': ['$n', 2]}
    }
},
{
    '$project': {
        'name': 1,
        'n': 1,
        'revenue': 1,
        'cost': {'$multiply': ['$revenue', 0.25]}
    }
}]

I did not find any better solution yet if you want to keep all previous computed fields, without reusing expressions several times.

like image 45
Maik Laschober Avatar answered Oct 25 '22 08:10

Maik Laschober


Starting in Mongo 3.4, you can use a series of $addFields stages rather than verbose $project stages.

This way, you can keep on projecting additional fields, some of which can re-use fields previously computed:

// { name: "a", n: 3 }
// { name: "b", n: 5 }
db.collection.aggregate([
  { $addFields: { revenue: { $multiply: ["$n", 2] } } },
  { $addFields: { cost: { $multiply: ["$revenue", 0.25] } } } // re-use "revenue"
])
// { name: "a", n: 3, revenue: 6,  cost: 1.5 }
// { name: "b", n: 5, revenue: 10, cost: 2.5 }
like image 2
Xavier Guihot Avatar answered Oct 25 '22 07:10

Xavier Guihot