Help update a document based on a document array containing a value and set a value based on another document field logic

Hello, I am blocked with the creation of an update request that need to filter on array containing an exact element and that need to set a value based on the value of another field of the document. Any help will be appreciated.

This is my data which represents a group that is ‘opened’ to be joined:

{
    "_id" : ObjectId("5fe9b45d3613f35f9c7fe012"),
    "status" : "creation",
    "type" : "opened",
    "invitedUsers" : [],
    "members" : [ 
        {
            "_id" : ObjectId("5fb71aebc0f0ea402cd8e007"),
            "role" : "capitain",
            "side" : "black",
        }
    ]
}

and another variant of a group that users can join only on ‘invitations’ if their user id is in the invitedUsers array:

{
    "_id" : ObjectId("5fe9b45d3613f35f9c7fe013"),
    "status" : "creation",
    "type" : "invitations",
    "invitedUsers" : [ObjectId("5fceb82c2917434bb8ef4966")],
    "members" : [ 
        {
            "_id" : ObjectId("5fb71aebc0f0ea402cd8e007"),
            "role" : "capitain",
            "side" : "black",
        }
    ]
}

I am trying to create the join action with an updateOne request to the DB collection
This request will filter on

  • the id of the group
  • the status which has to be ‘creation’
  • the type which has to be ‘opened’ or has to be ‘invitations’ in which case the invitedUsers array has to contain the id of the user.

the request will

  • set the status to ‘configuration’
  • add a member in the members array with:
    • _id set to the user id
    • role set to ‘challenger’
    • side set depending on the first member (capitain) side:
      • if the capitain side is black, set side to ‘white’
      • if the capitain side is white, set side to ‘black’
      • if the capitain side is aleatoire, set side to ‘aleatoire’

I would like to do it in one request because I am concerned by concurrent access (2 users that would like to join at the same time).

What I have tried is:

updateOne(
    {
      _id:ObjectId("5fe9b45d3613f35f9c7fe012"),
      status:'creation',
      $or : [{type:'opened'},{ $and: [{type:'invitations'}, {$elemMatch: {invitedUsers:ObjectId("5fceb82c2917434bb8ef4966")}}  ] }],
      members: {$size: 1},
    },
    {
      $set: {status:'configuration'},
      $push: {
        members: {
          _id: _id,
          side:
          { 
            $switch: {
              branches: [
                { case : { $eq: ["$members.0.side", 'white']}, then: "black" },
                { case : { $eq: ["$members.0.side", 'black']}, then: "white" },
                { case : { $eq: ["$members.0.side", 'aleatoire']}, then: "aleatoire" },
              ],
              default: ""
            }
          },
          role:'challenger',
          tchatMode:'ON'
        }
      }
    }
  )

And same with the second data (invitations case) changing only the _id of the group:

updateOne(
        {
          _id:ObjectId("5fe9b45d3613f35f9c7fe013"),
          status:'creation',
          $or : [{type:'opened'},{ $and: [{type:'invitations'}, {$elemMatch: {invitedUsers:ObjectId("5fceb82c2917434bb8ef4966")}}  ] }],
          members: {$size: 1},
        },
        {
          $set: {status:'configuration'},
          $push: {
            members: {
              _id: _id,
              side:
              { 
                $switch: {
                  branches: [
                    { case : { $eq: ["$members.0.side", 'white']}, then: "black" },
                    { case : { $eq: ["$members.0.side", 'black']}, then: "white" },
                    { case : { $eq: ["$members.0.side", 'aleatoire']}, then: "aleatoire" },
                  ],
                  default: ""
                }
              },
              role:'challenger',
              tchatMode:'ON'
            }
          }
        }
      )

But it seems that we can not use $elemMatch in the filter query (Robo 3T reports error: ‘unknown top level operator: $elemMatch"’) nor use members.0.side in the update query (Robo 3T reports error: 'The dollar () prefixed field ‘$switch’ in ‘members…side.$switch’ is not valid for storage.’)

This will have take me few minutes to be written in sql but I want to use mongoDB, understand the mongoDB concepts/usages and improve my capabilities to write all kinds of MongoDB requests. I have tried to find a solution in the mongoDB documentation and over the net but I found nothing to help me.

Any help will be appreciated.

Hi @Nicolas_L,

Welcome to MongoDB Community.

I think the best way to attack this scenario is using Pipeline updates available in 4.2+.

This way you will be able to use the strong ability of aggregations. I made the following update as an example:

db.groups.updateOne({_id:ObjectId("5fe9b45d3613f35f9c7fe012"), 
status:'creation',
$or : [{type:'opened'},{type:'invitations',invitedUsers : ObjectId("5fceb82c2917434bb8ef4966")}]
},
 [{$set: {
  status: 'configuration',
  members : {$concatArrays : ["$members",[{
    _id : ObjectId(),
    side : {$arrayElemAt : ["$members.side",0]},
    role:'challenger',
    tchatMode:'ON'
  }]]}
}}])

Please note that you don’t have to use $elemMatch to match a single filed or value in an array and use equal it.

Please let me know if you have any additional questions.

Best regards,
Pavel

1 Like

Thank you a lot @Pavel_Duchovny,

So, this is my final code (for those who would have a similar concern):

updateOne(
    {
      _id:ObjectId("5fe9b45d3613f35f9c7fe112"),
      status:'creation',
      $or : [{type:'opened'},{type:'invitations',invitedUsers : ObjectId("5fceb82c2917434bb8ef4966")}],
      members: {$size: 1},
    },
    [
      {
        $set: {
          status:'configuration',
          members: {
            $concatArrays : [ "$members",[{
              _id: ObjectId("5fceb82c2917434bb8ef4966"),
              side: {
                $switch: {
                  branches: [
                    { case : { $eq: [ {$arrayElemAt : ["$members.side",0]} , 'white']}, then: "black" },
                    { case : { $eq: [ {$arrayElemAt : ["$members.side",0]} , 'black']}, then: "white" },
                    { case : { $eq: [ {$arrayElemAt : ["$members.side",0]}, 'aleatoire']}, then: "aleatoire" },
                  ],
                  default: ""
                }
                  
              },
              role:'challenger',
            }] ]
          }
        }
      }
    ]
  )

So I do not need to use elemMatch within the array filter and I need to use an aggregation pipelines ( ) to get expressing conditional updates based on current field values or updating one field using the value of another field(s).

I have keeped the switch statement because I need to toggle black / white between capitain and challenger, the arrayElemAt help me a lot in this logical statement.

I have done some unit test with opened and invitations cases and it is working as expected :slight_smile:

Again, thanks you

1 Like

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