MongoDB insert document “or” increment field if ex

2020-07-06 03:59发布

问题:

What I try to do is fairly simple, I have an array inside a document ;

"tags": [ 
    {
        "t" : "architecture",
        "n" : 12
    }, 
    {
        "t" : "contemporary",
        "n" : 2
    }, 
    {
        "t" : "creative",
        "n" : 1
    }, 
    {
        "t" : "concrete",
        "n" : 3
    }
]

I want to push an array of items to array like

["architecture","blabladontexist"]

If item exists, I want to increment object's n value (in this case its architecture),

and if don't, add it as a new Item (with value of n=0) { "t": "blabladontexist", "n":0}

I have tried $addToSet, $set, $inc, $upsert: true with so many combinations and couldn't do it.

How can we do this in MongoDB?

回答1:

I don't believe this is possible to do in a single command.

MongoDB doesn't allow a $set (or $setOnInsert) and $inc to affect the same field in a single command.

You'll have to do one update command to attempt to $inc the field, and if that doesn't change any documents (n = 0), do the update to $set the field to it's default value.



回答2:

With MongoDB 4.2 and newer, the update method can now take a document or an aggregate pipeline where the following stages can be used:

  1. $addFields and its alias $set
  2. $project and its alias $unset
  3. $replaceRoot and its alias $replaceWith.

Armed with the above, your update operation with the aggregate pipeline will be to override the tags field by concatenating a filtered tags array and a mapped array of the input list with some data lookup in the map:

To start with, the aggregate expression that filters the tags array uses the $filter and it follows:

const myTags = ["architecture", "blabladontexist"];

{ 
    "$filter": { 
        "input": "$tags",
        "cond": { 
            "$not": [
                { "$in": ["$$this.t", myTags] } 
            ] 
        }
    } 
}

which produces the filtered array of documents

[  
    { "t" : "contemporary", "n" : 2 }, 
    { "t" : "creative", "n" : 1 }, 
    { "t" : "concrete", "n" : 3 }
]

Now the second part will be to derive the other array that will be concatenated to the above. This array requires a $map over the myTags input array as

{ 
    "$map": { 
        "input": myTags,
        "in": {
            "$cond": {
                "if": { "$in": ["$$this", "$tags.t"] },
                "then": { 
                    "t": "$$this", 
                    "n": { 
                        "$sum": [
                            { 
                                "$arrayElemAt": [
                                    "$tags.n", 
                                    { "$indexOfArray": [ "$tags.t", "$$this" ] } 
                                ] 
                            },
                            1
                        ]
                    } 
                },
                "else": { "t": "$$this", "n": 0 }
            }
        }
    } 
}

The above $map essentially loops over the input array and checks with each element whether it's in the tags array comparing the t property, if it exists then the value of the n field of the subdocument becomes its current n value expressed with

{ 
    "$arrayElemAt": [
        "$tags.n", 
        { "$indexOfArray": [ "$tags.t", "$$this" ] } 
    ] 
}

else add the default document with an n value of 0.

Overall, your update operation will be as follows

Your final update operation becomes:

const myTags = ["architecture", "blabladontexist"];

db.getCollection('coll').update(
    { "_id": "1234" },
    [
        { "$addToSet": {
            "tags": {
                "$concatArrays": [
                    { "$filter": { 
                        "input": "$tags",
                        "cond": { "$not": [ { "$in": ["$$this.t", myTags] } ] }
                    } },
                    { "$map": { 
                        "input": myTags,
                        "in": {
                            "$cond": [
                                { "$in": ["$$this", "$tags.t"] },
                                { "t": "$$this", "n": { 
                                    "$sum": [
                                        { "$arrayElemAt": [
                                            "$tags.n", 
                                            { "$indexOfArray": [ "$tags.t", "$$this" ] } 
                                        ] },
                                        1
                                    ]
                                } },
                                { "t": "$$this", "n": 0 }
                            ]
                        }
                    } }
                ]
            }
        } }
    ],
    { "upsert": true }
);


标签: mongodb