MongoDB:upsert子文档

she*_*sek 35 upsert mongodb mongodb-query

我有类似的文档,具有唯一索引bars.name:

{ name: 'foo', bars: [ { name: 'qux', somefield: 1 } ] }

.我想更新子文档在哪里{ name: 'foo', 'bars.name': 'qux' }$set: { 'bars.$.somefield': 2 },或者用{ name: 'qux', somefield: 2 }under 创建一个新的子文档{ name: 'foo' }.

是否可以使用带有upsert的单个查询来执行此操作,还是必须发出两个单独的查询?

相关:嵌入式文档中的"upsert"(建议更改模式以将子文档标识符作为键,但这是两年前的事情,我想知道现在是否有更好的解决方案.)

Nei*_*unn 44

不,没有真正更好的解决方案,所以也许有一个解释.

假设您有一个具有您显示的结构的文档:

{ 
  "name": "foo", 
  "bars": [{ 
       "name": "qux", 
       "somefield": 1 
  }] 
}
Run Code Online (Sandbox Code Playgroud)

如果你做这样的更新

db.foo.update(
    { "name": "foo", "bars.name": "qux" },
    { "$set": { "bars.$.somefield": 2 } },
    { "upsert": true }
)
Run Code Online (Sandbox Code Playgroud)

然后一切都很好,因为找到了匹配文档.但是如果你改变"bars.name"的值:

db.foo.update(
    { "name": "foo", "bars.name": "xyz" },
    { "$set": { "bars.$.somefield": 2 } },
    { "upsert": true }
)
Run Code Online (Sandbox Code Playgroud)

那你就会失败.这里唯一真正改变的是,在MongoDB 2.6及更高版本中,错误更简洁:

WriteResult({
    "nMatched" : 0,
    "nUpserted" : 0,
    "nModified" : 0,
    "writeError" : {
        "code" : 16836,
        "errmsg" : "The positional operator did not find the match needed from the query. Unexpanded update: bars.$.somefield"
    }
})
Run Code Online (Sandbox Code Playgroud)

这在某些方面更好,但你真的不想"upsert"无论如何.您要做的是将元素添加到当前不存在"name"的数组中.

所以你真正想要的是没有"upsert"标志的更新尝试的"结果",看看是否有任何文件受到影响:

db.foo.update(
    { "name": "foo", "bars.name": "xyz" },
    { "$set": { "bars.$.somefield": 2 } }
)
Run Code Online (Sandbox Code Playgroud)

屈服回应:

WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
Run Code Online (Sandbox Code Playgroud)

因此,当0您修改了文档时,您就知道要发布以下更新:

db.foo.update(
    { "name": "foo" },
    { "$push": { "bars": {
        "name": "xyz",
        "somefield": 2
    }}
)
Run Code Online (Sandbox Code Playgroud)

真的没有其他方法可以完全按照你的意愿去做.由于对阵列的添加并非严格意义上的"设置"操作类型,因此您无法$addToSet"批量更新"功能结合使用,因此您可以"级联"更新请求.

在这种情况下,您似乎需要检查结果,否则接受读取整个文档并检查是否更新或在代码中插入新的数组元素.

  • @MichaelMoser只看到了评论.但是通过添加不等式测试,如`{"name":"foo","bars.name":{"$ ne":"xyz"}}`作为查询,那么你确保你没有复制"推"元素. (8认同)
  • 如果有多个进程同时尝试更新,那么如何使这个原子化.似乎可能存在竞争条件,您可能会多次调用$ push并最终在阵列中输出多个记录.有没有办法以原子方式插入新值? (6认同)
  • 是的,这就是我现在所拥有的 - 如果找不到匹配的文件,那么`$ set`后跟'$ push` ......只是想知道是否有更好的方法来解决这个问题.感谢您的解释! (2认同)

Ped*_*ehl 6

我正在挖掘相同的功能,发现在 4.2 或更高版本中,MongoDB 提供了一个名为Update with Aggregation Pipeline的新功能。
如果将此功能与其他一些技术结合使用,则可以通过单个查询实现更新插入子文档操作。

这是一个非常冗长的查询,但我相信如果您知道 subCollection 上不会有太多记录,那么它是可行的。以下是如何实现此目标的示例:

const documentQuery = { _id: '123' }
const subDocumentToUpsert = { name: 'xyz', id: '1' }

collection.update(documentQuery, [
    {
        $set: {
            sub_documents: {
                $cond: {
                    if: { $not: ['$sub_documents'] },
                    then: [subDocumentToUpsert],
                    else: {
                        $cond: {
                            if: { $in: [subDocumentToUpsert.id, '$sub_documents.id'] },
                            then: {
                                $map: {
                                    input: '$sub_documents',
                                    as: 'sub_document',
                                    in: {
                                        $cond: {
                                            if: { $eq: ['$$sub_document.id', subDocumentToUpsert.id] },
                                            then: subDocumentToUpsert,
                                            else: '$$sub_document',
                                        },
                                    },
                                },
                            },
                            else: { $concatArrays: ['$sub_documents', [subDocumentToUpsert]] },
                        },
                    },
                },
            },
        },
    },
])
Run Code Online (Sandbox Code Playgroud)


nes*_*dis 5

如果您不介意稍微更改架构并具有如下结构:

{ "name": "foo", "bars": { "qux": { "somefield": 1 },
                           "xyz": { "somefield": 2 },
                  }
}
Run Code Online (Sandbox Code Playgroud)

您可以一口气完成您的操作。在嵌入式文档中重申“upsert”以确保完整性