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
}

1 Answer 1

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
        }
    }
])
Sign up to request clarification or add additional context in comments.

12 Comments

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..
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 docs.mongodb.com/manual/reference/operator/aggregation/slice & docs.mongodb.com/manual/reference/operator/aggregation/proje‌​ct
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!
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.
Yeah it is possible. I have added that as an update.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.