0

I am relatively new to Mongo, and trying to figure out how to complete this query. I'm using mongoose, in node.js. I want to get the sums of sell.amount, and buy.amount, from this document with a nested array of objects. The trades array is large, and I want to limit it to the first (from 0 ) n number of objects. I want to be able to get returned something like:

{buy.totalAmount, buy.tradesCount},{sell.totalAmount, sell.tradesCount}, 

as a sum of the number of elements selected by $limit

I figure I must be close, but so far I cannot figure out how to make this work.

My Query:

tradePair.aggregate(
        {$match: {pair:"STOCK"}},
        {$unwind:"$trades"},
        {$limit: 20},
        {$group : { _id : '$trades.buy', 
            count : {$sum : 1},
            totalAmount: { $sum: '$trades.buy.amount' }
        }},
        function(err,result){
          return result
        }
)

My Database Document, showing only 2 trade array elements..of many...

{
    "_id" : ObjectId("58fa86c81cdd7b2375cdd4cc"),
    "pair" : "STOCK",
    "trades" : [ 
        {
            "sell" : {
                "trades" : 1,
                "total" : 0.13309789,
                "amount" : 24.80139,
                "rate" : 0.00536655
            },
            "buy" : {
                "trades" : 0,
                "total" : 0,
                "amount" : 0,
                "rate" : 0
            },
            "_id" : ObjectId("58fa87290567372b4035d16f"),
            "endTradeId" : 41306,
            "startTradeId" : 41306,
            "dateEnd" : ISODate("2017-04-21T21:37:39.000Z"),
            "dateStart" : ISODate("2017-04-21T21:37:39.000Z")
        }, 
        {
            "sell" : {
                "trades" : 2,
                "total" : 1.23879614,
                "amount" : 230.83659924,
                "rate" : 0.00536655
            },
            "buy" : {
                "trades" : 0,
                "total" : 0,
                "amount" : 0,
                "rate" : 0
            },
            "_id" : ObjectId("58fa87290567372b4035d16e"),
            "endTradeId" : 41305,
            "startTradeId" : 41304,
            "dateEnd" : ISODate("2017-04-21T21:35:28.000Z"),
            "dateStart" : ISODate("2017-04-21T21:35:27.000Z")
        }, 
        ..., 
        ..., 

    ],
    "lastTradeId" : 41306,
    "dateStart" : ISODate("2017-04-21T21:37:39.000Z"),
    "dateEnd" : ISODate("2017-04-21T21:37:39.000Z"),
    "__v" : 0
}
sbiefeni
  • 53
  • 1
  • 6

1 Answers1

1

You can use below aggregation pipeline with Mongo 3.4.

The below code will use $match stage to keep matching documents.

$project uses $slice on trades array to return 20 elements followed by $reduce which takes the array values and sum the value for each document.

db.tradePair.aggregate([{
        $match: {
            pair: "STOCK"
        }
    },
    {
        $project: {
            trades: {
                $reduce: {
                    "input": {
                        $slice: ["$trades", 20]
                    },
                    initialValue: {
                        buyTotalAmount: 0,
                        buyTradesCount: 0,
                        sellTotalAmount: 0,
                        sellTradesCount: 0
                    },
                    in: {
                        buyTotalAmount: {
                            $add: ["$$value.buyTotalAmount", "$$this.buy.amount"]
                        },
                        buyTradesCount: {
                            $add: ["$$value.buyTradesCount", "$$this.buy.trades"]
                        },
                        sellTotalAmount: {
                            $add: ["$$value.sellTotalAmount", "$$this.sell.amount"]
                        },
                        sellTradesCount: {
                            $add: ["$$value.sellTradesCount", "$$this.sell.trades"]
                        }
                    }
                }
            },
            _id: 0
        }
    }
])

Update:

Add avg, min and max in the same pipeline for sell.trades field.

The below query will set the initialValue to the first element of $trades.sell.tradesand followed by $lt comparison on the sell.trades and if true set $$this to $$value, if false keep the previous value and $reduce all the values to find the minimum element and $gt to find the max element with the similar logic. Also, added the count field to keep track of number of trade entry. The outermost $let which reads the result of inner $reduce along with dividing sumsellTradesCount with count to calculate avg.

db.tradePair.aggregate([{
        $match: {
            pair: "STOCK"
        }
    },
    {
        $project: {
            trades: {
                $let: {
                    vars: {
                        obj: {
                            $reduce: {
                                "input": {
                                    $slice: ["$trades", 20]
                                },
                                initialValue: {
                                    minsellTradesCount: {
                                        $let: {
                                            vars: {
                                                obj: {
                                                    $arrayElemAt: ["$trades", 0]
                                                }
                                            },
                                            in: "$$obj.sell.trades"
                                        }
                                    },
                                    maxsellTradesCount: {
                                        $let: {
                                            vars: {
                                                obj: {
                                                    $arrayElemAt: ["$trades", 0]
                                                }
                                            },
                                            in: "$$obj.sell.trades"
                                        }
                                    },
                                    sumsellTradesCount: 0,
                                    count: 0
                                },
                                in: {
                                    sumsellTradesCount: {
                                        $add: ["$$value.sumsellTradesCount", "$$this.sell.trades"]
                                    },
                                    minsellTradesCount: {
                                        $cond: [{
                                            $lt: ["$$this.sell.trades", "$$value.minsellTradesCount"]
                                        }, "$$this.sell.trades", "$$value.minsellTradesCount"]
                                    },
                                    maxsellTradesCount: {
                                        $cond: [{
                                            $gt: ["$$this.sell.trades", "$$value.minsellTradesCount"]
                                        }, "$$this.sell.trades", "$$value.minsellTradesCount"]
                                    },
                                    count: {
                                        $add: ["$$value.count", 1]
                                    }
                                }
                            }
                        }
                    },
                    in: {
                        minsellTradesCount: "$$obj.minsellTradesCount",
                        maxsellTradesCount: "$$obj.maxsellTradesCount",
                        sumsellTradesCount: "$$obj.sumsellTradesCount",
                        avgsellTradesCount: {
                            $divide: ["$$obj.sumsellTradesCount", "$$obj.count"]
                        }
                    }
                }
            },
            _id: 0
        }
    }
])

Update 2:

Add avg, min and max in the same pipeline for sell.amount field.

db.tradePair.aggregate([{
        $match: {
            pair: "STOCK"
        }
    },
    {
        $project: {
            trades: {
                $let: {
                    vars: {
                        obj: {
                            $reduce: {
                                "input": {
                                    $slice: ["$trades", 20]
                                },
                                initialValue: {
                                    minsellTotalAmount: {
                                        $let: {
                                            vars: {
                                                obj: {
                                                    $arrayElemAt: ["$trades", 0]
                                                }
                                            },
                                            in: "$$obj.sell.amount"
                                        }
                                    },
                                    maxsellTotalAmount: {
                                        $let: {
                                            vars: {
                                                obj: {
                                                    $arrayElemAt: ["$trades", 0]
                                                }
                                            },
                                            in: "$$obj.sell.amount"
                                        }
                                    },
                                    sumsellTotalAmount: 0,
                                    count: 0
                                },
                                in: {
                                    minsellTotalAmount: {
                                        $cond: [{
                                            $lt: ["$$this.sell.amount", "$$value.minsellTotalAmount"]
                                        }, "$$this.sell.amount", "$$value.minsellTotalAmount"]
                                    },
                                    maxsellTotalAmount: {
                                        $cond: [{
                                            $gt: ["$$this.sell.amount", "$$value.maxsellTotalAmount"]
                                        }, "$$this.sell.amount", "$$value.maxsellTotalAmount"]
                                    },
                                    sumsellTotalAmount: {
                                        $add: ["$$value.sumsellTotalAmount", "$$this.sell.amount"]
                                    },
                                    count: {
                                        $add: ["$$value.count", 1]
                                    }
                                }
                            }
                        }
                    },
                    in: {
                        minsellTotalAmount: "$$obj.minsellTotalAmount",
                        maxsellTotalAmount: "$$obj.maxsellTotalAmount",
                        sumsellTotalAmount: "$$obj.sumsellTotalAmount",
                        avgsellTotalAmount: {
                            $divide: ["$$obj.sumsellTotalAmount", "$$obj.count"]
                        }
                    }
                }
            },
            _id: 0
        }
    }
])
s7vr
  • 73,656
  • 11
  • 106
  • 127
  • Thanks Veeram, I'll give this a shot tomorrow and see what my result is. The $project thing is a little mysterious to me. A couple of questions.. will $slice grab the top 20 (0 - 19) or the last 20? Also, is it possible to for example grab a slice from index (5 - 25) for example.. skipping the first few documents.. – sbiefeni Apr 22 '17 at 20:53
  • You are welcome. $project to keep/remove fields in the pipeline. Answer to first question: top 20. Use `$slice:[array, -20]` for last 20.. Answer to second question: Yes, `$slice:[array, 5, 25]`. More examples and info https://docs.mongodb.com/manual/reference/operator/aggregation/slice/ & https://docs.mongodb.com/manual/reference/operator/aggregation/proje‌​ct – s7vr Apr 22 '17 at 21:07
  • Great Veeram... everything works as expected, as well this will be a very good template for me to start making more queries into the data.. thank you so much! – sbiefeni Apr 23 '17 at 18:48
  • Hi Veeram, you seem very knowledgeable.. I wonder if you know how I can also do things like return an average, minimum, and maximum Amount, where I am summing Amounts. I notice the mongo arithmetic operators don't seem to allow directly doing this. – sbiefeni Apr 23 '17 at 20:05
  • Yeah it is possible. I have added that as an update. – s7vr Apr 23 '17 at 21:32
  • Ok.. it's getting totally greek to me. How were you able to learn mongo queries so proficiently? Are there any good tools or resources to learn to query as well as you do? – sbiefeni Apr 23 '17 at 22:00
  • The mongodb examples in the documentation are really nice. Look at the all aggregation stages/ operators and start with those examples. Please ask me if you don't understand anything and I'll happy to help you and add more explanation if you need. – s7vr Apr 23 '17 at 22:06
  • I completely appreciate your input Veeram.. sorry if I'm not getting this yet. I tried to modify your latest to get the min, max, and average on the amounts, not counts.. however I'm still off base and can't make it work. I definitely have a lack of understanding of this whole structure. How could your code be adapted to handle the amounts, rather than the counts? – sbiefeni Apr 23 '17 at 23:39
  • Np. Updated answer to show the similar logic for `sell.amount` field. – s7vr Apr 23 '17 at 23:46
  • Veeram, thank you very much.. you are truly a wizard with mongo.. I hope I can learn more from you in the near future.. – sbiefeni Apr 24 '17 at 01:09
  • Hi Veeram, not sure how I would reach you directly so I'm replying here: I have another problem which I hope you can solve: In the same data, I am trying to return one document, with a sub-set of the trades array - I want to filter the trades array to only those which are in a date range I specify. I am trying this query: .aggregate({$match:{pair:"STOCK"}},{$unwind: '$trades'},{$match:{"trades.dateStart": {$gt:"2017-04-24 18:30:00.000Z"}}}) but of course... I'm not able to make it work.. – sbiefeni Apr 24 '17 at 18:51
  • There are more than couple of ways resolving this one. http://stackoverflow.com/questions/15117030/how-to-filter-array-in-subdocument-with-mongodb. Use the `$filter` variant. – s7vr Apr 24 '17 at 19:02