skip to Main Content

Inspired by another question, suppose I have documents that contain an array of events, where each event has 3 fields: eventName, actorId, and detail:

  {
    _id: ObjectId("5a934e000102030405000000"),
    date: ISODate("2022-06-03T00:00:00.000Z"),
    events: [
      {
        eventName: "Add",
        actorId: "1",
        detail: "new actor"
      },
      {
        eventName: "Vote",
        actorId: "2",
        detail: "up"
      },
      {
        eventName: "Vote",
        actorId: "3",
        detail: "down"
      },
      {
        eventName: "Vote",
        actorId: "4",
        detail: "cork"
      }
    ]
  }

I want to add new items into a specific document (according to its _id), but each item can be added only if none of the existing items as the same eventName and actorId unique combination.

For example trying to add:

[
    {
     eventName: "Add",
     actorId: "3",
     detail: "action"
   },
   {
     eventName: "Vote",
     actorId: "2",
     detail: "up"
   },
   {
     eventName: "Vote",
     actorId: "5",
     detail: "up"
   }
] 

Will result in adding only the 1st and 3rd item, as a combination of eventName: "Vote" and actorId: "2" already exists in the events array.

2

Answers


  1. Chosen as BEST ANSWER

    Since monogDB version 4.2, this can be done using an update with aggregation pipeline:

    1. $addFields with newData to add and create a new keys field that contains the existing unique keys concatanted.
    2. $filter the newData to include only items with keys that are not in the original array keys.
    3. $concatArrays: events and the filtered newData array.
    db.collection.update({},
    [
      {
        $addFields: {
          newData: [
            {
              eventName: "Add",
              actorId: "3",
              detail: "action"
            },
            {
              eventName: "Vote",
              actorId: "2",
              detail: "up"
            },
            {
              eventName: "Vote",
              actorId: "5",
              detail: "up"
            }
          ],
          keys: {
            $map: {
              input: "$events",
              as: "item",
              in: {$concat: ["$$item.eventName", "$$item.actorId"]}
            }
          }
        }
      },
      {
        $set: {
          newData: {
            $filter: {
              input: "$newData",
              as: "item",
              cond: {
                $not: {
                  $in: [
                    {$concat: ["$$item.eventName", "$$item.actorId"]},
                    "$keys"
                  ]
                }
              }
            }
          }
        }
      },
      {
        $set: {
          events: {$concatArrays: ["$events","$newData"]},
          newData: "$$REMOVE",
          keys: "$$REMOVE"
        }
      }
    ])
    

    Playground example


  2. Here is a way of performing the update operation using Updates With Aggregation Pipeline feature (requires MongoDB v4.2 or higher). The variable NEW_EVENTS represents the new data that is input as an array of documents (e.g., [ { "eventName" : "Vote", "actorId" : "5", "detail" : "up" }, { ... }, ... ] ).

    db.collection.updateMany(
    { },    // query filter
    [
      { 
          $set: { 
              events: {
                  $reduce: {
                      input: NEW_EVENTS,
                      initialValue: "$events",
                      in: {
                          $cond: {
                              if: {
                                  $eq: [ 
                                      { $size: {
                                          $filter: {
                                              input: "$events",
                                              as: "ev",
                                              cond: {
                                                  $and: [
                                                      { $eq: [ "$$this.eventName", "$$ev.eventName" ] },
                                                      { $eq: [ "$$this.actorId", "$$ev.actorId" ] }
                                                  ]
                                              }
                                          } 
                                      }}, 0
                                  ]
                              },
                              then: { $concatArrays: [ "$$value", [ "$$this" ] ] },
                              else: "$$value"
                          }
                      }
                  }
              }
          }
      }
    ])
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search