Question

I have an "orders" collection which is like this:

{ typeID: 1, buyOrder: true, price: 100 },
{ typeID: 1, buyOrder: false, price: 120 },
{ typeID: 1, buyOrder: false, price: 130 },
{ typeID: 1, buyOrder: false, price: 250 },
{ typeID: 2, buyOrder: true, price: 500 },
{ typeID: 2, buyOrder: false, price: 610 },
{ typeID: 2, buyOrder: false, price: 690 },
{ typeID: 2, buyOrder: false, price: 590 }

and I want to aggregate this collection and find the best buy/sell price for each typeid.

The result should be:

{ typeID: 1, bestBuy: 100, bestSell: 120 }
{ typeID: 2, bestBuy: 500, bestSell: 610 }

Define bestBuy / bestSell

bestBuy  = (buyOrder = true && max price)
bestSell = (buyOrder = false && min price)

This is what I have so far but I know that its wrong. Any ideas ?

db.orders.aggregate([
    { $sort : { typeID : 1 }},
    { $group: 
        { _id: { typeID : "$typeID", buyOrder : "$buyOrder"},
        price: { $max: "$price" },
        }
     },
     { $project:
         { _id: 0,
             typeID: "$_id.typeID",
             price: "$price",
             buyOrder: "$_id.buyOrder",
         }
     }
    ])

Thanks for your time.

Was it helpful?

Solution

You may not yet be aware of the $cond operator which works as a ternary condition. So basically if a condition given a as a first argument is true then use the value in the next argument. If the condition evaluates to false then use the value in the last condition in the operator.

This turns out to be perfect as you already have an indicator of true or false to determine the field

db.orders.aggregate([
    { "$project": {
        "typeID": 1,
        "bestBuy": { "$cond": [
            "$buyOrder",
            "$price",
            null
        ]},
        "bestSell": { "$cond": [
            "$buyOrder",
            null,
            "$price"
        ]}
    }},
    { "$group": {
        "_id": "$typeID",
        "bestBuy": { "$max": "$bestBuy" },
        "bestSell": { "$min": "$bestSell" }
    }},
    { "$sort": { "_id": 1 } }
])

So the use of $max and $min here can negate the null values in the results where the condition was not met.

OTHER TIPS

Maybe with a mapreduce you can achieve this with something like that :

var mapFunction1 = function() {
 emit(this.typeID , this.buyOrder, this.price);
};

var reduceFunction1 = function(key, values) {
 reducedValue = { bestBuy: 0, bestSell: 0 };
 for (var idx = 0; idx < values.length; idx++) {
  if(values[idx].buyOrder && reducedValue.bestBuy < values[idx].price) {
   reducedValue.bestBuy = values[idx].price
  }
  if(!values[idx].buyOrder && reducedValue.bestSell > values[idx].price) {
   reducedValue.bestSell = values[idx].price
  }
 }
 return reducedValue;
};

db.orders.mapReduce(
 mapFunction1,
 reduceFunction1,
 { out: "your_result" }
)

Hope it helped

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top