首页 > 解决方案 > 是否可以使用聚合管道进行 $setOnInsert ?

问题描述

MongoDB 最近添加了一个选项,update通过提供聚合管道而不是标准修饰符对象来执行操作。检查 MongoDB 的有关此主题的文档

使用聚合管道(其语句可以引用现有文档属性)的能力在需要基于其他字段评估某些字段的情况下非常有用,例如在数据迁移期间。

此外,大多数标准更新运算符,如$set, $push,$inc等都可以使用聚合表达式语言成功复制,因此在某种意义上,这个新功能概括了良好的旧修饰符技术。不过,我必须承认,如果尝试执行诸如$addToSet. 这当然会带来一大堆与性能相关的问题,但现在让我们忽略它们。

到目前为止,只有一件事我无法通过聚合管道更新完全复制,即$setOnInsert操作员。假设我要执行 upsert:

db.test.update(selector, pipeline, { upsert: true });

我最初的直觉是,除非存在匹配的文档,否则$$ROOT变量(我可以在 中使用pipeline)将相等。不幸的是,但可能有一个很好的理由,MongoDB 开发人员决定默认情况下应该从它派生。当您考虑正常的工作原理时,这是有道理的,但它也使得实际上无法区分 update 和 insert within 。nullselector$$ROOTselector$setOnInsertpipeline

我知道你在想什么。你可以看看$$ROOT._id。这是一个好主意,尽管如果它_id是其中的一部分,selector它就不再起作用了。我发现这可以通过稍微欺骗 MongoDB 并执行以下操作来绕过:

selector = {
  _id: { $in: [value, 'fake'] },
}

相反,如果更简单{ _id: value },但这看起来并不干净。请注意,如果$in只包含一个元素,那么 Mongo 实际上足够聪明,可以找出标识符应该是什么并$$ROOT相应地填充(原文如此!)。

我想知道是否有人对如何解决这个问题有更好的想法。也许有一些隐藏变量我可以在其内部使用pipeline来区分updateinsert(例如,在$merge阶段有$$new用于类似目的的变量)?

标签: mongodbmongodb-queryaggregation-frameworkaggregation

解决方案


如果没有匹配的文件,$$ROOT将只有_id字段。因此,您可以$$ROOT通过其键/值对转换为数组,并检查该数组的大小是否等于 1。如果是则创建一个新文档,如果不是则什么也不做。

  • $objectToArray并通过其键/值对$size$$ROOT转换为数组并获取该数组的大小
  • $cond检查上面数组的大小是否等于 1。如果是,则将当前$$ROOT(仅_id字段)与更新对象合并。如果不是,则返回当前的$$ROOT。在这两种情况下,将结果放入结果字段。
  • $mergeObjects合并$$ROOT和您发送的更新,并将其放在结果字段中
  • $replaceRoot将根替换为上一阶段的结果字段
db.collection.update({
  _id: 1
},
[
  {
    $set: {
      result: {
        $cond: {
          if: {
            "$eq": [
              {
                $size: {
                  $objectToArray: "$$ROOT"
                },
                
              },
              1
            ]
          },
          then: {
            $mergeObjects: [
              "$$ROOT",
              {
                key: 3
              }
            ]
          },
          else: "$$ROOT"
        },
        
      }
    }
  },
  {
    $replaceRoot: {
      newRoot: "$result"
    }
  }
],
{
  upsert: true
})

工作示例


推荐阅读