Eric Guan
Eric Guan

Reputation: 15982

Mongodb conditionally update doubly nested array

My collection looks like this. A document contains an array of suggestions. Each suggestion can have an array of emotes. A user can only emote once per suggestion. Note that a document can have multiple suggestions

{
    id: 1,
    suggestions:[
        {
            id: 463,
            emotes:[
                {
                    id: 35,
                    userId: 2
                },
                {
                    id: 23,
                    userId: 3
                },
            ]
        },
        ...
    ]
},
...

emotes can only contain 1 instance of a userId. I need to check if userId exists in emotes before pushing a new emote element.

My attempted solutions:

(I'm using the update command syntax to access MongoDB 3.6 features.)

First attempt: addToSet will not work because the emote array elements are keyed by userId, not the entire object.

db.get().command({
    update: 'channels',
    updates:[
        {
            q:{ 
                channelId, 
                  "suggestions.id": suggestionOid,
            },
            u:{
                $addToSet:
                {
                    "suggestions.$[s].emotes": data
                }
            },
            arrayFilters:[
                { 's.id': suggestionOid }
            ]
        }
    ]
})

Second attempt: The below doesn't work. The user could already have an emote for another suggestion, which will cause the $ne query condition to fail. Which means the user can only emote one suggestion per document, they should be able to emote all suggestions for a document.

db.get().command({
    update: 'channels',
    updates:[
        {
            q:{ 
                channelId, 
                  'suggestions.emotes.userId': {$ne: userId },
                  "suggestions.id": suggestionOid,
            },
            u:{
                $push:
                {
                    "suggestions.$[s].emotes": data
                }
            },
            arrayFilters:[
                { 's.id': suggestionOid }
            ]
        }
    ]
})

I am open to moving emotes to a new collection or changing its structure, if that makes things easier. I can't change anything else though.

Upvotes: 2

Views: 988

Answers (1)

Eric Guan
Eric Guan

Reputation: 15982

I solved this by using the $elemMatch operator.

return channels.updateOne(
    {
        channelId, 
        "suggestions":{
            $elemMatch:{
                id: suggestionOid,
                'emotes.user': {$ne: user }
            }
        },
    },
    {
        $push:
        {
            "suggestions.$.emotes": data
        }
    }
)

Important bit from the mongodb docs https://docs.mongodb.com/manual/reference/operator/update/positional/

If the query matches the array using a negation operator, such as $ne, $not, or $nin, then you cannot use the positional operator to update values from this array.

However, if the negated portion of the query is inside of an $elemMatch expression, then you can use the positional operator to update this field.

Upvotes: 2

Related Questions