Reputation: 62886
I have invoices, where each invoice contains a list of items. Each item has (among other things) the following fields:
And each invoice has (among other things) the fields:
The invoices live in a dedicated Mongo collection, called invoices.
I would like to get all the invoices containing the specified item, where for each invoice the following information needs to be returned:
Let us call the tuple <id, date, qty, total> as the invoice projection. Thus the result should be a list of invoice projections.
If an invoice lists the given item twice, then the respective invoice yields two projection instances. If an invoice does not list the given item at all, then this invoice is not present in the result.
Anyway, I am retrieving the required projections using the following Mongo aggregation pipeline:
pipeline = [
{$match: {'items.name': req.params.name}},
{$project: {created: 1, 'items.name': 1, 'items.qty': 1, 'items.total': 1}},
{$unwind: '$items'},
{$match: {'items.name': req.params.name}},
{$project: {created: 1, qty: '$items.qty', total: '$items.total'}}
],
The pipeline works like this:
items.name
, so this $match
is efficient.{_id: ?, created: ?, items: [{name: ?, qty: ?, total: ?}, ..., {name: ?, qty: ?, total: ?}]}
items
array, now we have a list of {_id: ?, created: ?, 'items.name': ?, 'items.qty': ?, 'items.total': ?}
objects.The final list of invoice projections is then put through the following code:
function prepareResult(projections) {
var res = projections.reduce(function (acc, item) {
acc.itemCount += item.qty;
acc.total += item.total;
delete item.total;
return acc;
}, {itemCount: 0, total: 0});
res.items = projections;
return res;
}
It sums the qty
and total
fields over all of the invoice projections and returns a new object containg the projections as well as the calculated sums. The total
field is deleted from each and every invoice projection, since it is only the final sum that is needed.
My question - can I move the logic of the prepareResult
function into the Mongo aggregation pipeline?
Upvotes: 1
Views: 2998
Reputation: 42352
You need to add a $group step to the pipeline.
The _id of group will be what you are summing by (in this case a constant since you want a grand total). Since you want to retain the list of invoices, you can accumulate them into an array field via the $push operator. Sums for total and quantity would be handled with $sum.
{$group : { _id : 1,
Total : { $sum : "$total" },
ItemCount : { $sum : "$qty" },
Invoices : { $push : { id : "$_id", created : "$created" }}
} }
Upvotes: 2