1

I have a MongoDB collection with documents that look like:

{
    '_id': 'doc1',
    'store_A': {'apples': 50, 'oranges':20},
    'store_B': {'oranges': 15}
}
{
    '_id': 'doc2',
    'store_A': {'oranges':10},
    'store_B': {'apples': 15}
}

How can I write an aggregation command to give me the total number of fruits for each store across all documents in the collection WITHOUT enumerating all allowed kinds of fruit?

The result should look like:

{
    '_id': 'Result',
    'store_A_total': {'apples': 50, 'oranges': 30},
    'store_B_total': {'apples': 15, 'oranges': 15}
}

This query works, but all the fruit types must be specified explicitly:

db.collection.aggregate(
{'$group': {'_id': 'Result',
    'store_A_apples': {'$sum': '$Store_A.apples'},
    'store_A_oranges': {'$sum': '$store_A.oranges'},
    'store_B_apples': {'$sum': '$store_B.apples'},
    'store_B_oranges': {'$sum': '$store_B.oranges'}
}},
{'$project': {
    'store_A': {'apples': '$store_A_apples','oranges': '$store_A_oranges'},
    'store_B': {'apples': '$store_B_apples','oranges': '$store_B_oranges'}
}})

Is there a better way to structure these documents to facilitate this type of query?

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
SuperAce99
  • 712
  • 6
  • 13

1 Answers1

5

There isn't a way in the mongodb aggregation framework for treating a key inside of a document as data you can examine or manipulate. A workaround is to turn what you're using as keys here (e.g. fruit type and store name) into values like this:

{
    "_id" : "doc1",
    "stores":[
        {
            // store name is a value
            "name":"store_A",
            "inventory": [
            {
                // so is fruit type
                "type" : "apple",
                "count" : 50
            },
            {
                "type" : "orange",
                "count" : 20
            }
            ]
        },
        {
            "name": "store_B",
            "inventory": [
            {
                "type" : "orange",
                "count" : 15
            }
            ]
        }
    ]
}

This allows you to work with these data more easily in aggregation:

db.coll.aggregate([
    // split documents by store name
    {$unwind:"$stores"},
    // split documents further by fruit type
    {$unwind:"$stores.inventory"},
    // group documents together by store/fruit type, count quantities of fruit
    {$group:{"_id":{"store":"$stores.name", "fruit":"$stores.inventory.type"},
             "count":{$sum:"$stores.inventory.count"}}},
    // reformat the data to look more like your specification
    {$project:{
        "store":"$_id.store",
        "fruit":"$_id.fruit",
        "_id":0,
        "count":1}}])

The output looks like:

{
    "result" : [
        {
            "count" : 15,
            "store" : "store_B",
            "fruit" : "apple"
        },
        {
            "count" : 15,
            "store" : "store_B",
            "fruit" : "orange"
        },
        {
            "count" : 30,
            "store" : "store_A",
            "fruit" : "orange"
        },
        {
            "count" : 50,
            "store" : "store_A",
            "fruit" : "apple"
        }
    ],
    "ok" : 1
}
llovett
  • 1,449
  • 1
  • 12
  • 21