Home > Back-end >  Mongoose/MongoDB: How can I $inc only the first value I get from an array?
Mongoose/MongoDB: How can I $inc only the first value I get from an array?

Time:01-05

I have a Mongoose Schema that looks like this:

{
 _id: ObjectID,
 storage: [{
    location: String,
    storedFood: [{
      code: String,
      name: String,
      weight: Number
    }]
 }]
}

And for example in storedFood can be the same Food twice. But I only want to update one of the weights of these items. This is my code to $inc all of the items.... How can I reduce this to only one?

try{
    const deletedFoodFromStorage = await User.updateOne(
        {_id: user, "storage.location": location},
        { $inc: {"storage.$.storedFood.$[food].weight": -weight}},
        { arrayFilters: [ { "food.code": code } ]},
    );
    res.json(deletedFoodFromStorage);
}catch(err){
    res.status(400).json('Error: '   err)

}

CodePudding user response:

Should have been a simple one. Only way I found at the moment is not simple:

db.collection.update(
  {_id: user, "storage.location": location},
  [
    {$set: {
      newItem: {
        $reduce: {
          input: {$getField: {
              input: {$first: {$filter: {
                        input: "$storage",
                        as: "st",
                        cond: {$eq: ["$$st.location", location]}
              }}},
              field: "storedFood"
          }},
          initialValue: [],
          in: {$concatArrays: [
              "$$value",
              {$cond: [
                  {$and: [
                      {$eq: ["$$this.code", code]},
                      {$not: {$in: [code, "$$value.code"]}}
                  ]},
                  [{$mergeObjects: [
                        "$$this",
                        {weight: {$subtract: ["$$this.weight", weight]}}
                  ]}],
                  ["$$this"]
                ]
              }
            ]
          }
        }
      }
  }},
  {$set: {
      storage: {
        $map: {
          input: "$storage",
          in: {$cond: [
              {$eq: ["$$this.location", location]},
              {$mergeObjects: ["$$this", {storedFood: "$newItem"}]},
              "$$this"
          ]}
        }
      },
      newItem: "$$REMOVE"
  }}
])

See how it works on the playground example

CodePudding user response:

Borrowing liberally from nimrod serok's answer, here's one way to do it with a single pass through all the arrays. I suspect this can be improved, at least for clarity.

db.collection.update({
  _id: user,
  "storage.location": location
},
[
  {
    "$set": {
      "storage": {
        "$map": {
          "input": "$storage",
          "as": "store",
          "in": {
            "$cond": [
              {"$ne": ["$$store.location", location]},
              "$$store",
              {
                "$mergeObjects": [
                  "$$store",
                  {
                    "storedFood": {
                      "$getField": {
                        "field": "theFoods",
                        "input": {
                          "$reduce": {
                            "input": "$$store.storedFood",
                            "initialValue": {
                              "incOne": false,
                              "theFoods": []
                            },
                            "in": {
                              "$cond": [
                                "$$value.incOne",
                                {
                                  "incOne": "$$value.incOne",
                                  "theFoods": {
                                    "$concatArrays": [
                                      "$$value.theFoods",
                                      ["$$this"]
                                    ]
                                  }
                                },
                                {
                                  "$cond": [
                                    {"$ne": ["$$this.code", code]},
                                    {
                                      "incOne": "$$value.incOne",
                                      "theFoods": {
                                        "$concatArrays": [
                                          "$$value.theFoods",
                                          ["$$this"]
                                        ]
                                      }
                                    },
                                    {
                                      "incOne": true,
                                      "theFoods": {
                                        "$concatArrays": [
                                          "$$value.theFoods",
                                          [
                                            {
                                              "$mergeObjects": [
                                                "$$this",
                                                {"weight": {"$add": ["$$this.weight", -weight]}}
                                              ]
                                            }
                                          ]
                                        ]
                                      }
                                    }
                                  ]
                                }
                              ]
                            }
                          }
                        }
                      }
                    }
                  }
                ]
              }
            ]
          }
        }
      }
    }
  }
])

Try it on mongoplayground.net.

  • Related