Juan
Juan

Reputation: 432

MongoDB - How to use elemMatch as condition to update fields

I have two arrays of objects in my documents.

{
  "profileSpherestatus": [
    {
      "idf": "B002",
      "completedPercentage": 100
    },
    {
      "idf": "B003",
      "completedPercentage": 90
    },
    {
      "idf": "B004",
      "completedPercentage": 100
    }
  ]
  "myGratificacions": [
    {
      "idb": "B003",
      "gratification": 20
    },
    {
      "idb": "B004",
      "gratification": 30
    }
  ]
}

I want to add a new object in myGratifications if there is at least one object in the profileSpherestatus array where idf is "B002" and completedPercentage is 100, and there is not a gratification with "idb": "B002".

The newObject is:

newObject = {
  "idb":"B002",
  "gratification":10
}

This is my pipeline:

[
  {"$set": {
    "myGratifications":
      {"$cond": {
        "if": {
          "$and": [
            {"profileSpherestatus": 
              {"$elemMatch": {"idf": "B002", "completedPercentage": 100}}
            },
            {"$not": 
              {"myGratifications":{"$elemMatch": {"idb": "B002"}}}
            }
          ]
        },
        "then": {
            "$concatArrays": ["$myGratifications",[newObject]]
        },
        "else": "$myGratifications"
    }}
  }} 
]

But $elemMatch cannot be used inside $set or $addFields. Can you help me?

Upvotes: 1

Views: 53

Answers (2)

aneroid
aneroid

Reputation: 16112

If you are only doing this one update - adding the new element to myGratifications - then you can add your update-only-if conditions in the filter/match query for update.

Combine the element checks with any other query you already have, like on _id, etc. In the update part, you can use the $push array update operator to add the new element.

It has the added benefit of not doing any update when the document is found but the arrays don't meet the criteria you've defined. May not make much of a difference for a single document update but if you wanted to update a thousands/millions, then documents which exist but don't meet the criteria would be skipped.

db.collection.update({
  // match criteria
  "$and": [
    // other filter criteria START
    {
      // as an example
      "_id": { "$regex": ".*" }
    },
    // other filter criteria END
    // ACTUAL filter criteria to update or not
    {
      "profileSpherestatus": {
        "$elemMatch": {
          "idf": "B002",
          "completedPercentage": 100
        }
      }
    },
    {
      "myGratifications": {
        "$not": {
          "$elemMatch": { "idb": "B002" }
        }
      }
    }
  ]
},
{
  // update operation
  "$push": {
    "myGratifications": { "idb": "B002", "gratification": 10 }
  }
},
{ multi: true }
)

Mongo Playground with additional test docs


Btw, you can still use your current set+concat method for the update part if you want, or have other $set operations to do. The match/filter query remains the same as above & the update part is similar to your existing then clause:

[
  // update operations
  {
    "$set": {
      "myGratifications": {
        "$concatArrays": [
          "$myGratifications", [{ "idb": "B002", "gratification": 10 }]
        ]
      }
    }
  }
]

mongo playground

Upvotes: 1

Yong Shun
Yong Shun

Reputation: 51450

$elemMatch is not supported when you use an aggregation pipeline.

  • Replace the first $elemMatch condition by checking the $size of the $filter array.

  • Replace the second $elemMatch condition with $not $in.

db.collection.update({ /* match criteria */ },
[
  {
    "$set": {
      "myGratificacions": {
        "$cond": {
          "if": {
            "$and": [
              {
                $gt: [
                  {
                    $size: {
                      $filter: {
                        input: "$profileSpherestatus",
                        cond: {
                          $and: [
                            {
                              $eq: [
                                "$$this.idf",
                                "B002"
                              ]
                            },
                            {
                              $eq: [
                                "$$this.completedPercentage",
                                100
                              ]
                            }
                          ]
                        }
                      }
                    }
                  },
                  0
                ]
              },
              {
                "$not": {
                  $in: [
                    "B002",
                    "$myGratificacions.idb"
                  ]
                }
              }
            ]
          },
          "then": {
            "$concatArrays": [
              "$myGratificacions",
              [
                {
                  "idb": "B002",
                  "gratification": 10
                }
              ]
            ]
          },
          "else": "$myGratificacions"
        }
      }
    }
  }
])

Demo @ Mongo Playground

Note that there is a typo error in your current update query, myGratifications field doesn't exist in your document, should be myGratificacions.

Upvotes: 1

Related Questions