Search code examples
mongodbmongoosemongodb-queryaggregation-framework

Reduce an array of elements into an object in MongoDB


I have a MongoDB collection named Venue with elements of type:

{
    venue: "Grand Hall",
    sections: [{
        name: "Lobby",
        drinks: [{
            name: "Vodka",
            quantity: 3
        }, {
            name: "Red Wine",
            quantity: 1
        }]
    }, {
        name: "Ballroom",
        drinks: [{
            name: "Vodka",
            quantity: 22
        }, {
            name: "Red Wine",
            quantity: 50
        }]
    }]
}

I want to calculate the total amounts of each drink for the party. So I want my result to be something like that:

{
    venue: "Grand Hall",
    sections: 2,
    drinks: [{
        name: "Vodka",
        quantity: 25
    }, {
        name: "Red Wine",
        quantity: 51
    }]
}

Solution

  • There are lots of ways to do this. Here's another one using "$reduce" and "$map", etc.

    db.Venue.aggregate({
      "$match": {
        "venue": "Grand Hall"
      }
    },
    {
      "$set": {
        "sections": {"$size": "$sections"},
        "drinks": {
          "$reduce": {
            "input": {  // flatten array of drink objects
              "$reduce": {
                "input": "$sections.drinks",
                "initialValue": [],
                "in": {"$concatArrays": ["$$value", "$$this"]}
              }
            },
            "initialValue": [],
            "in": {
              "$let": {
                "vars": {  // position of drink in $$value, or -1 if not found
                  "idx": {"$indexOfArray": ["$$value.name", "$$this.name"]}
                },
                "in": {
                  "$cond": [
                    {"$eq": ["$$idx", -1]},  // not found?
                    {"$concatArrays": ["$$value", ["$$this"]]},  // not found, add object
                    {  // found, so update object in $$value by summing quantities
                      "$map": {
                        "input": "$$value",
                        "as": "val",
                        "in": {
                          "$cond": [
                            {"$eq": ["$$val.name", "$$this.name"]},  // right object?
                            {  // yes, update quantity
                              "name": "$$val.name",
                              "quantity": {"$sum": ["$$val.quantity", "$$this.quantity"]}
                            },
                            "$$val"  // wrong object for update, so just keep it
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    })
    

    Try it on mongoplayground.net.