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:
$addFields
with newData to add and create a newkeys
field that contains the existing unique keys concatanted.$filter
thenewData
to include only items with keys that are not in the original array keys.$concatArrays
:events
and the filterednewData
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"
}
}
])
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"
}
}
}
}
}
}
])