MongoDB插入文档“或”如果数组中存在,则增加字段

11

我想做的事情非常简单,我有一个文档中的数组;

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

我希望你能将一组项目推到数组中,就像这样:
["architecture","blabladontexist"]

如果该项存在,我想要增加对象的n值(在这种情况下是architecture),
如果不存在,则将其作为新项目添加(值为n=0{"t":"blabladontexist","n":0}
我尝试了许多组合,使用$addToSet$set$inc$upsert: true等方法,但都无法实现。
我们如何在MongoDB中实现这个功能?

你看过聚合管道吗? - Josh Beam
1
我猜聚合不是解决问题的方法,因为它只是从集合中选择数据。@Sadettin Bilal Savaş需要插入数据。只有当集合的结构发生变化时才有原因。 - Den Kison
你是否考虑将数据结构进行更改作为解决方案?例如,你可以在tags数组中引用Tag实体的ID。 - Josh Beam
嘿,乔希,如果没有其他方法,当然可以开放进行更改。我只是试图弄清楚是否可以通过单个查询来解决我的问题,因为在MongoDB中,类似于upsert的操作非常常见,而我找不到一种简单的更新方法。你所说的标记实体ID是什么意思?它会有什么帮助吗?谢谢! - Sadettin Bilal Savaş
2个回答

2
使用MongoDB 4.2及更高版本,更新方法现在可以接受一个文档或聚合管道,其中可以使用以下阶段:
  1. $addFields 和它的别名 $set
  2. $project 和它的别名 $unset
  3. $replaceRoot 和它的别名 $replaceWith

掌握了上述内容,您使用聚合管道进行更新操作时,将通过连接筛选后的 tags 数组和映射数组来覆盖 tags 字段,并在映射中查找一些数据:

首先,过滤tags数组的聚合表达式使用了 $filter 并且如下所示:
const myTags = ["architecture", "blabladontexist"];

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

它会生成过滤后的文档数组。
[  
    { "t" : "contemporary", "n" : 2 }, 
    { "t" : "creative", "n" : 1 }, 
    { "t" : "concrete", "n" : 3 }
]

现在的第二部分是派生出另一个数组,该数组将与上述数组连接。这个数组需要对输入数组myTags进行$map处理:
{ 
    "$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 }
            }
        }
    } 
}

上述代码中的$map本质上是循环遍历输入数组,并检查每个元素是否在tags数组中,比较t属性,如果存在,则子文档的n字段的值变为其当前的n值。
{ 
    "$arrayElemAt": [
        "$tags.n", 
        { "$indexOfArray": [ "$tags.t", "$$this" ] } 
    ] 
}

否则添加默认文档,n值为0。
总体而言,您的更新操作如下:
您的最终更新操作如下:
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 }
);

3
这完全是错误的!!在UPDATE事务的$set操作中怎么可能有concatarrays运算符??Concatarrays只能在聚合管道中使用。 - user2405589
1
他说得有道理,因为你显然从未运行过最终结果。使用聚合管道是正确的。但是你需要用方括号[]包装第二个参数,否则它不会知道你打算使用聚合管道。 - Lucas Manzke
1
此外,如果您在数据库中运行此代码,即使使用括号,最终结果也将是{ "_id" : "1234", "tags" : null },这不是预期的输出。 - Lucas Manzke

0

我不相信这可以在一个命令中完成。

MongoDB不允许在一个命令中使用$set(或$setOnInsert)和$inc影响同一字段。

您需要执行一个更新命令来尝试$inc该字段,如果没有更改任何文档(n = 0),则执行更新以将字段设置为其默认值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接