skip to Main Content

On below data set, I want to find "pId=123" and "gmData.CarData.cars.oid=’car_1’", if found then update "gmData.CarData.cars.dt" with new value, else Push document in "gmData.CarData.cars".

{
    "_id" : ObjectId("65a52a03ed163a5b8a06a80d"),
    "pId" : 123,
    "fv" : "1.2.3",
    "gmData" : {
        "CarData" : {
            "cars" : [
                {
                    "oid" : "car_1",
                    "dt" : {
                        "id" : "car_1",
                        "age" : 123,
                        "add" : "add_1"
                    }
                },
                {
                    "oid" : "car_2",
                    "dt" : {
                        "id" : "car_2",
                        "age" : 234,
                        "add" : "add_2"
                    }
                },
                {
                    "oid" : "car_3",
                    "dt" : {
                        "id" : "car_3",
                        "age" : 345,
                        "add" : "add_3"
                    }
                }
            ]
        }
    }
}

Tried so far using:

db.test.updateOne(
    { $and: [{pId:123}, {"gmData.CarData.cars.oid":"car_1"}],},
    {
        "$set":{"gmData.CarData.cars.$[curr].dt":{"id":"car_11","age":1123,"add":"add_11"}}
    },
    {
        arrayFilters: [ { "curr.oid": { $eq: "car_1" } } ]
    }
    )

Above query is working, if "gmData.CarData.cars.oid.car_1" exists. But I want to perform Push object .dt":{ "id" : "car_11","age" : 1123,"add" : "add_11"}} in "gmData.CarData.cars", if not exist.

2

Answers


  1. I’ve worked on an answer without arrayFilters.

    Here is what i have :

    db.collection.update({
      "pId": 123
    },
    [
      {
        "$set": {
          "gmData.CarData.cars": {
            "$cond": {
              if: {
                $in: [
                  "car_1",
                  {
                    "$ifNull": [
                      "$gmData.CarData.cars.oid",
                      []
                    ]
                  }
                ]
              },
              then: {
                $map: {
                  input: "$gmData.CarData.cars",
                  in: {
                    $cond: {
                      if: {
                        $eq: [
                          "$$this.oid",
                          "car_1"
                        ]
                      },
                      then: {
                        $mergeObjects: [
                          "$$this",
                          {
                            "dt": {
                              "id": "car_11",
                              "age": 1123,
                              "add": "add_11"
                            }
                          }
                        ]
                      },
                      else: "$$this"
                    }
                  }
                }
              },
              else: {
                "$concatArrays": [
                  [
                    {
                      "oid": "car_1",
                      "dt": {
                        "id": "car_11",
                        "age": 1123,
                        "add": "add_11"
                      }
                    }
                  ],
                  {
                    $ifNull: [
                      "$gmData.CarData.cars",
                      []
                    ]
                  }
                ]
              }
            }
          }
        }
      }
    ],
    {
      "upsert": true
    })
    

    It will look for the document with pId == 123 and update or create the car where oid == 'car_1'.

    You can try it for yourself on mongoplayground.

    Login or Signup to reply.
  2. There are two basic ways to solve this:


    Update with aggregation pipeline

    The most efficient way is to use a single update operation using an aggregation pipeline (see also @Fourchette’s answer). I have also put together a MongoPlayground that contains a similar aggregation pipeline. In the first stage, the cars array is initialized as an empty array if it does not exist yet:

      {
        $set: {
          "gmData.CarData.cars": {
            $ifNull: [
              "$gmData.CarData.cars",
              []
            ]
          }
        }
      },
    

    Doing this in an extra step simplifies the following stage that upserts the array element using a complex expression:

      {
        $set: {
          "gmData.CarData.cars": {
            $cond: {
              if: {
                $in: [
                  "car_1",
                  "$gmData.CarData.cars.oid"
                ]
              },
              then: {
                $map: {
                  input: "$gmData.CarData.cars",
                  as: "car",
                  in: {
                    $cond: {
                      if: {
                        $eq: [
                          "$$car.oid",
                          "car_1"
                        ]
                      },
                      then: {
                        $mergeObjects: [
                          "$$car",
                          {
                            dt: {
                              "id": "car_11",
                              "age": 1123,
                              "add": "add_11"
                            }
                          }
                        ]
                      },
                      else: "$$car"
                    }
                  }
                }
              },
              else: {
                $concatArrays: [
                  "$gmData.CarData.cars",
                  [
                    {
                      oid: "car_1",
                      dt: {
                        "id": "car_11",
                        "age": 1123,
                        "add": "add_11"
                      }
                    }
                  ]
                ]
              }
            }
          }
        }
      }
    

    The expression first checks whether a sub-document with a matching id exists; in this case, a $map expression maps each array element. If the id of the array element matches the one you are looking for, the dt property is updated. Otherwise the original array element is returned.

    In case there is no array element with a matching id, a $concatArray expression adds the new sub-document to the array.


    Multiple update statements

    A simpler approach is to use two update statements: the first statement looks for a document that already has an element with a matching id:

    db.collection.update({
      pId: 123,
      "gmData.CarData.cars.oid": "car_1"
    },
    {
      $set: {
        "gmData.CarData.cars.$.dt": {
          "id": "car_11",
          "age": 1123,
          "add": "add_11"
        }
      }
    })
    

    (mongoplayground)

    It is important to check the result of the operation. If a document was matched and modified, the following operation can be omitted. Otherwise, you’d issue another update statement – this time checking for the pId without a matching oid value in the cars array. In this case, the new item is pushed into the array:

    db.collection.update({
      pId: 123,
      "gmData.CarData.cars.oid": {
        $ne: "car_1"
      }
    },
    {
      $push: {
        "gmData.CarData.cars": {
          oid: "car_1",
          dt: {
            "id": "car_11",
            "age": 1123,
            "add": "add_11"
          }
        }
      }
    })
    

    (mongoplayground)

    Please note that the second approach is safe against race conditions due to the filters for the update statements. However, it is not as efficient as the update with an aggregation pipeline, but maybe better understandable for team members that are not experienced with MongoDB.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search