1



I want to make an upsert call to update as well as insert my data in nested array of mongo db.

This is my mongo document.

  {
    "_id" : "575",
    "_class" : "com.spyne.sharing.SpyneShareUserProject",
    "spyneSharePhotoList" : [ 
        {
            "_id" : "fxLO68XyMR",
            "spyneShareUsers" : [ 
                {
                    "_id" : "chittaranjan@eventila.com",
                    "selectedByClient" : false
                }, 
                {
                    "_id" : "chittaranjan@gmail.com",
                    "selectedByClient" : false
                }
            ]
        }, 
        {
            "_id" : "nVpD0KoQAI",
            "spyneShareUsers" : [ 
                {
                    "_id" : "chittaranjan@eventila.com",
                    "selectedByClient" : true
                }
            ]
        }, 
        {
            "_id" : "Pm0B3Q9Igv",
            "spyneShareUsers" : [ 
                {
                    "_id" : "chittaranjan@gmail.com",
                    "selectedByClient" : true
                }
            ]
        }
    ]
}

Here my requirement is,

  1. lets say i have an ID i.e. 575 (_id)
  2. Then i will have the nested array ID i.e. fxLO68XyMR (spyneSharePhotoList._id)
  3. Then i will have nested email id as ID i.e. chittaranjan@eventila.com (spyneSharePhotoList.spyneShareUsers._id) and selectedByClient (boolean)

Now i want is to check if this ID (spyneSharePhotoList.spyneShareUsers._id) is already present in the desired location i want to update the boolean value i.e. selectedByClient (true / false) according to that email id.

If the id is not present in the array, the it will make a new entry. as

{
 "_id" : "chittaranjan@gmail.com",
 "selectedByClient" : false
}

in spyneShareUsers list.

Please help me to do this task. Thank you

Mr Freak
  • 216
  • 3
  • 20

1 Answers1

0

Most likely this is not the smartest solution, but it should work:

shareUserProject = {
   id: "575",
   PhotoListId: "fxLO68XyMR",
   ShareUserId: "chittaranjan_new@eventila.com"
}

db.collection.aggregate([
   { $match: { _id: shareUserProject.id } },
   {
      $facet: {
         root: [{ $match: {} }],
         update: [
            { $unwind: "$spyneSharePhotoList" },
            { $match: { "spyneSharePhotoList._id": shareUserProject.PhotoListId } },
            {
               $set: {
                  "spyneSharePhotoList.spyneShareUsers": {
                     $concatArrays: [
                        {
                           $filter: {
                              input: "$spyneSharePhotoList.spyneShareUsers",
                              cond: { $ne: ["$$this._id", shareUserProject.ShareUserId] }
                           }
                        },
                        [{
                           _id: shareUserProject.ShareUserId,
                           selectedByClient: { $in: [shareUserProject.ShareUserId, "$spyneSharePhotoList.spyneShareUsers._id"] }
                        }]
                     ]
                  }
               }
            }
         ]
      }
   },
   { $unwind: "$root" },
   { $unwind: "$update" },
   {
      $set: {
         "root.spyneSharePhotoList": {
            $concatArrays: [
               ["$update.spyneSharePhotoList"],
               {
                  $filter: {
                     input: "$root.spyneSharePhotoList",
                     cond: { $ne: ["$$this._id", shareUserProject.PhotoListId] }
                  }
               }
            ]
         }
      }
   },
   { $replaceRoot: { newRoot: "$root" } }
]).forEach(function (doc) {
   db.collection.replaceOne({ _id: doc._id }, doc);
})

I did not check whether all operators are available in MongoDB 3.5

My goal was to process everything in aggregation pipeline and run just a single replaceOne() at the end.

Here another solution based on $map operator:

db.collection.aggregate([
  { $match: { _id: shareUserProject.id } },
  {
    $set: {
      spyneSharePhotoList: {
        $map: {
          input: "$spyneSharePhotoList",
          as: "photoList",
          in: {
            $cond: {
              if: { $eq: [ "$$photoList._id", shareUserProject.PhotoListId ] },
              then: {
                "_id": "$$photoList._id",
                spyneShareUsers: {
                  $cond: {
                    if: { $in: [ shareUserProject.ShareUserId, "$$photoList.spyneShareUsers._id" ] },
                    then: {
                      $map: {
                        input: "$$photoList.spyneShareUsers",
                        as: "shareUsers",
                        in: {
                          $cond: {
                            if: { $eq: [ "$$shareUsers._id", shareUserProject.ShareUserId ] },
                            then: { _id: shareUserProject.ShareUserId, selectedByClient: true },
                            else: "$$shareUsers"
                          }
                        }
                      }
                    },
                    else: {
                      $concatArrays: [
                        "$$photoList.spyneShareUsers",
                        [ { _id: shareUserProject.ShareUserId, selectedByClient: false } ]
                      ]
                    }
                  }
                }
              },
              else: "$$photoList"
            }
          }
        }
      }
    }
  }
]).forEach(function (doc) {
   db.collection.replaceOne({ _id: doc._id }, doc);
})

You can achieve the same result also with two updates:

shareUserProject = {
   id: "575",
   PhotoListId: "fxLO68XyMR_x",
   ShareUserId: "chittaranjan_new@gmail.com"
}

ret = db.collection.updateOne(
   { _id: shareUserProject.id },
   { $pull: { "spyneSharePhotoList.$[photoList].spyneShareUsers":  { _id: shareUserProject.ShareUserId } } },
   { arrayFilters: [{ "photoList._id": shareUserProject.PhotoListId }] }
)


db.collection.updateOne(
   { _id: shareUserProject.id },
   { $push: { "spyneSharePhotoList.$[photoList].spyneShareUsers":  { _id: shareUserProject.ShareUserId, selectedByClient: ret.modifiedCount == 1 } } },
   { arrayFilters: [{ "photoList._id": shareUserProject.PhotoListId }] }
)
Wernfried Domscheit
  • 54,457
  • 9
  • 76
  • 110
  • i was trying the $map script, but i am getting this error - Error: command failed: { "ok" : 0, "errmsg" : "Unrecognized pipeline stage name: '$set'", "code" : 40324, "codeName" : "Location40324" } – Mr Freak Feb 13 '20 at 06:10
  • getting same error for the 1st script also @WernfriedDomscheit – Mr Freak Feb 13 '20 at 06:18
  • Try to replace `$set` with `$addFields`. `$set` was introduced in Mongo 4.2 and is an alias for `$addFields`. All other operators should be available in 3.5. In general they are working, see [Mongo Playground](https://mongoplayground.net/p/VDiRLUXNiGA) and [Mongo Playground](https://mongoplayground.net/p/E7q04p9AxSd). – Wernfried Domscheit Feb 13 '20 at 06:45