Home > Enterprise >  mongoDb - How to ensure unique item in an array based on specific fields?
mongoDb - How to ensure unique item in an array based on specific fields?

Time:06-06

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.

CodePudding user response:

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

CodePudding user response:

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"
                      }
                  }
              }
          }
      }
  }
])
  • Related