【问题标题】:Is it possible to $setOnInsert with aggregation pipeline?是否可以使用聚合管道进行 $setOnInsert ?
【发布时间】:2021-10-29 14:11:02
【问题描述】:

MongoDB 最近添加了一个选项,通过提供聚合管道而不是标准修饰符对象来执行 update 操作。 Check MongoDB's docs on this topic.

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

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

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

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

我最初的直觉是$$ROOT 变量(我可以在pipeline 中使用)将等于null,除非存在与selector 匹配的文档。不幸的是,MongoDB 开发人员认为$$ROOT 应该默认派生自selector,这可能是有充分理由的。当您考虑$setOnInsert 的正常工作方式时,这是有道理的,但它实际上也使得在pipeline 中区分更新和插入几乎是不可能的。

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

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

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

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

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework aggregation


    【解决方案1】:

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

    • $objectToArray$size 通过其键/值对将 $$ROOT 转换为数组并获取该数组的大小
    • $cond 检查上面数组的大小是否等于 1。如果是,则将当前 $$ROOT(仅是 _id 字段)与 更新对象。如果不是,则返回当前的 $$ROOT。在这两种情况下,将结果放在 result 字段中。
    • $mergeObjects 合并 $$ROOT 和您发送的 update,并将其放入 result 字段
    • $replaceRoot 将 root 替换为上一阶段的 result 字段
    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
    })
    

    Working example

    【讨论】:

    • 我明白你在这里想要做什么。在更通用的情况下,即当selector 还包含除_id 之外的其他字段时,$$ROOT 对象也将具有其他字段,理论上我可以以类似的方式将它们考虑在内。不过,我面临的问题是我想要一个通用的解决方案,可以与任何可能的selector 一起使用。坦率地说,要弄清楚 MongoDB 通常如何将selector 映射到$$ROOT 似乎非常困难。
    • 也许你可以直接问他们。
    • 我实际上希望他们可能会审查 StackOverflow 上标记为 mongodb 的问题 :) 但你说得对,我的帖子可能更好地描述为 v5.0 的“功能请求”而不是常规问题。
    猜你喜欢
    • 2017-07-26
    • 2020-02-12
    • 1970-01-01
    • 2020-08-31
    • 2016-10-26
    • 1970-01-01
    • 2012-07-14
    • 1970-01-01
    相关资源
    最近更新 更多