MongoDB 4.2 update pipeline: “Push or Update” in nested array

I want push new object to nested array if object with field ‘Cat’=1 not exists. Or if object with ‘Cat’=1 field exists then increment Up field in this object.

I cant write right syntax, help plz

Document:

{
  "BlogId" : 1,
  "Items" : [
    {
      "Cat" : 1,
      "Up" : 555,
    }, 
    {
      "Cat" : 2,
      "Up" : 666,
    }
  ]
}

Query (with wrong syntax):

db.exp.update({ BlogId: 1 }, 
[
  {
     $set: {
          "Items.Cat": {
            $cond: [ 
                     { "Items.Cat": 1 } , 
                     { $inc:  { "Items.Up": 1 } }, 
                     { $push: { "Items": { Cat: 1, Up: 555 }} } 
                   ]
          }
     }
  }
])

The update query:

db.exp.update( { BlogId: 1 },
  [ 
      { 
          $set: { 
              Items: {
                  $reduce: {
                      input: { $ifNull: [ "$Items", [] ] }, 
                      initialValue: { items: [], update: false },
                      in: {
                          $cond: [ { $eq: [ "$$this.Cat", INPUT_DOC.Cat ] },
                                   { 
                                     items: { 
                                        $concatArrays: [
                                            "$$value.items",
                                            [ { Cat: "$$this.Cat", Up: { $add: [ "$$this.Up", 1 ] } } ],
                                        ] 
                                      }, 
                                      update: true
                                   },
                                   { 
                                      items: { 
                                         $concatArrays: [ "$$value.items", [ "$$this" ] ] 
                                      }, 
                                      update: "$$value.update" 
                                   }
                          ]
                      }
                  }
              }
          }
      },
      { 
          $set: { 
              Items: { 
                  $cond: [ { $eq: [ "$Items.update", false ] },
                           { $concatArrays: [ "$Items.items", [ INPUT_DOC ] ] },
                           { $concatArrays: [ "$Items.items", [] ] }
                  ] 
              }
          }
      }
  ] 
)

The update does the following:

  • if the Cat exists increments the Up value by 1
  • if the Cat not exists adds the INPUT_DOC to the Items array
  • If the Items array doesn’t exist, creates the array and adds the INPUT_DOC

Try with the object values: INPUT_DOC = { Cat: 1, Up: 555 }, or INPUT_DOC = { Cat: 3, Up: 888 }

1 Like

WOW thx.
But why so hardy for easzy operation. amazing… with $reduce, $$this, $concatArrays, $$value :grimacing:
Intresting can we optimize for more light code query

You are welcome :smile:

The operation uses an aggregation instead of an update - and there is some logic involved (find-and-modify-or-insert), hence all the code. And, this doesn’t allow the update operators like, $set, $inc, $push, etc.

The $set used above is not an operator - it is an aggregation pipeline stage. The operators used within aggregation are different, are easy to use and can derive complex programming logic using objects, dates, arrays, conditions, strings, etc - which is not possible with direct update.

Without using aggregation, we can end up using two operations (and lighter code) instead of one operation. As such this update operation is atomic.

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.