Question

For example, i have following document:

{
    "_id" : ObjectId("52ffddd3c69e40174c046d67"),
    "works" : [ 
        {
            "service_id" : ObjectId("52ffd576c69e40174c046d64"),
            "price" : 150,
            "count" : 3,
            "items" : [ 
                {
                    "price" : 5,
                    "amount" : 10
                }, 
                {
                    "price" : 7,
                    "amount" : 3
                }
            ]
        }, 
        {
            "service_id" : ObjectId("52ffd56fc69e40174c046d63"),
            "price" : 100,
            "count" : 2
        }
    ]
}

Where service_id refers to some outer document (i know, it's not recommended), price is price for work, count is how many times work have been performed, and items is array for materials, used in all works. I need to get total price for all works. I tried following unfinished code:

db.service_cards.aggregate(
    { $match: {_id: ObjectId("52ffddd3c69e40174c046d67") } },
    { $unwind: "$works" },
    { $project: {
        "works.service_id": 1,
        "works.price": 1,
        "works.count": 1,
        "works.items": { 
            "$cond": [ 
                { $eq: [ "$works.items", []] },
                [{
                    "price": 0,
                    "amount": 0
                }],
                "$works.items"
            ],
        }
    } },
    { $project: {
        "works.service_id": 1,
        "works.price": 1,
        "works.count": 1,
        "works.items": { 
            "$ifNull": [ 
                "$works.items",
                [{
                    "price": 0,
                    "amount": 0
                }]
            ],
        }
    } },
    { $unwind: "$works.items" },
    { $project: { 
        "service_id": "$works.service_id",
        "price": "$works.price",
        "count": "$works.count",
        "items_cost": { $multiply: ["$works.items.price", "$works.items.amount"] },
    } },
    { $group: { 
        _id: "$service_id", 
        "sum_items": { $sum: "$items_cost" },
        "price": { $avg: "$price" },
        "count": { $avg: "$count" }
    } }
)

It doesn't actually sum arrays - it unwinds works and items, then uses $group to sum unwinded items. But then i remembered - there could be two works with equal service_id, count and price, and after $unwind and $group, i'll lose one of them, putting items from both together.

Is there any way to get array $sum inside document without $unwind?

Edit: I expect to receive a list of works without their items, but with total items cost:

{
    "_id" : ObjectId("52ffddd3c69e40174c046d67"),
    "works" : [ 
        {
            "service_id" : ObjectId("52ffd576c69e40174c046d64"),
            "price" : 150,
            "count" : 3,
            "items_cost" : 71
        }, 
        {
            "service_id" : ObjectId("52ffd56fc69e40174c046d63"),
            "price" : 100,
            "count" : 2,
            "items_cost": 0
        }
    ]
}
Was it helpful?

Solution

I think, it is not better solution, but it is short and doing what you want:

db.service_cards.find().forEach(function (document) {
    var works = document.works || [];
    var result = [];
    works.forEach(function (work) {
        var items = work.items || [];
        var itemsCost = 0;
        items.forEach(function (item) {
            itemsCost += item.price * item.amount;
        });
        var resultObj = {
            service_id: work.service_id,
            price: work.price,
            count: work.count,
            items_cost: itemsCost
        };
        result.push(resultObj);
    });
    print({
        _id: document._id,
        works: result
    });
})
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top