Query to update an array field by using another array field of same document in mongodb

Viewed 157

Scenario

I've the following document from Chat collection with an array of messages and members in the chat.

And for each message, there will be status field which will store the delivered and read timestamp with respect to users.

{
    "_id" : ObjectId("60679797b4365465745065b2"),
    "members" : [ 
        ObjectId("604e02033f4fc07b6b82771c"), 
        ObjectId("6056ef4630d7b103d8043abd"), 
        ObjectId("6031e3dce8934f11f8c9a79c")
    ],
    "isGroup" : true,
    "createdAt" : 1617401743720.0,
    "updatedAt" : 1617436504453.0,
    "messages" : [ 
        {
            "createdAt" : 1617401743719.0,
            "updatedAt" : 1617401743719.0,
            "_id" : ObjectId("60679797b4365465745065b3"),
            "body" : "This is test message",
            "senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
            "status" : []
        }
    ]
}

So, I want to insert the following data, into messages.status array, to know when the message is received/read by the member.

{
    receiverId: <member of chat>
    deliveredAt: <timestamp>
    readAt: <timestamp>
}

Question

How to write a query to insert the above json for each member (except the sender) in the status array by using the data from existing field?

So that, after query, the document should look like this:

{
    "_id" : ObjectId("60679797b4365465745065b2"),
    "members" : [ 
        ObjectId("604e02033f4fc07b6b82771c"), 
        ObjectId("6056ef4630d7b103d8043abd"), 
        ObjectId("6031e3dce8934f11f8c9a79c")
    ],
    "isGroup" : true,
    "createdAt" : 1617401743720.0,
    "updatedAt" : 1617436504453.0,
    "messages" : [ 
        {
            "createdAt" : 1617401743719.0,
            "updatedAt" : 1617401743719.0,
            "_id" : ObjectId("60679797b4365465745065b3"),
            "body" : "This is test message",
            "senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
            "status" : [{
                "receiverId": ObjectId("604e02033f4fc07b6b82771c")
                "deliveredAt": <timestamp>
                "readAt": <timestamp>
            }, {
                "receiverId": ObjectId("6056ef4630d7b103d8043abd")
                "deliveredAt": <timestamp>
                "readAt": <timestamp>
            }]
        }
    ]
}

Edit

I'm able to do this for static data.

Link: https://mongoplayground.net/p/LgVPfRoXL5p

For easy understanding: I've to map the members array and insert it into the status field of the messages

MongoDB Version: 4.0.5

2 Answers

You can use the $function operator to define custom functions to implement behavior not supported by the MongoDB Query Language. So along with updates-with-aggregate-pipeline and $function you can update messages.status array with only receiver's details as shown below:

NOTE: Works only with MongoDB version >= 4.4.

Try this:

let messageId = ObjectId("60679797b4365465745065b3");

db.chats.update(
  { "messages._id": messageId },
  [
    {
      $set: {
        "messages": {
          $map: {
            input: "$messages",
            as: "message",
            in: {
              $cond: {
                if: { $eq: ["$$message._id", messageId] },
                then: {
                  $function: {
                    body: function (message, members) {
                      message.status = [];
                      for (let i = 0; i < members.length; i++) {
                        if (message.senderId.valueOf() != members[i].valueOf()) {
                          message.status.push({
                            receiverId: members[i],
                            deliveredAt: new Date().getTime(),
                            readAt: new Date().getTime()
                          })
                        }
                      }

                      return message;
                    },
                    args: ["$$message", "$members"],
                    lang: "js"
                  }
                },
                else: "$$message"
              }
            }
          }
        }
      }
    }
  ]
);

Output:

{
    "_id" : ObjectId("60679797b4365465745065b2"),
    "members" : [
        ObjectId("604e02033f4fc07b6b82771c"),
        ObjectId("6056ef4630d7b103d8043abd"),
        ObjectId("6031e3dce8934f11f8c9a79c")
    ],
    "isGroup" : true,
    "createdAt" : 1617401743720,
    "updatedAt" : 1617436504453,
    "messages" : [
        {
            "_id" : ObjectId("60679797b4365465745065b3"),
            "createdAt" : 1617401743719,
            "updatedAt" : 1617401743719,
            "body" : "This is test message",
            "senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
            "status" : [
                {
                    "receiverId" : ObjectId("604e02033f4fc07b6b82771c"),
                    "deliveredAt" : 1617625735318,
                    "readAt" : 1617625735318
                },
                {
                    "receiverId" : ObjectId("6056ef4630d7b103d8043abd"),
                    "deliveredAt" : 1617625735318,
                    "readAt" : 1617625735318
                }
            ]
        },
        {
            "_id" : ObjectId("60679797b4365465745065b4"),
            "createdAt" : 1617401743719,
            "updatedAt" : 1617401743719,
            "body" : "This is test message",
            "senderId" : ObjectId("6031e3dce8934f11f8c9a79d"),
            "status" : [ ]
        }
    ]
}

Demo - https://mongoplayground.net/p/FoOvxXp6nji

https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/

The filtered positional operator $[] identifies the array elements that match the arrayFilters conditions for an update operation, e.g.

db.collection.update({
  "messages.senderId": "6031e3dce8934f11f8c9a79c" // query 
},
{
  "$push": {
    "messages.$[m].status": [ // push into the matching element of arrayFilters
      {
        "receiverId": ObjectId("604e02033f4fc07b6b82771c")
      },
      {
        "receiverId": ObjectId("6056ef4630d7b103d8043abd")
      }
    ]
  }
},
{
  arrayFilters: [
    {
      "m.senderId": "6031e3dce8934f11f8c9a79c" // matches array element where senderId is 6031e3dce8934f11f8c9a79c
    }
  ]
})

Note- add index to messages.senderId for performance

Related