MongoDB insert document “or” increment field if exists in array

后端 未结 2 1860
执笔经年
执笔经年 2021-02-20 14:55

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

\"tags\": [ 
    {
        \"t\" : \"architecture\",
        \"n\" : 12
          


        
相关标签:
2条回答
  • 2021-02-20 15:04

    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" },
        [
            { "$set": {
                "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 }
    );
    
    0 讨论(0)
  • 2021-02-20 15:16

    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.

    0 讨论(0)
提交回复
热议问题