【问题标题】:Reference value from positional element in array in update更新中数组中位置元素的参考值
【发布时间】:2020-05-19 19:07:27
【问题描述】:

假设我有一个如下所示的文档:

{
    "id": 1,
    "entries": [
        {
            "id": 100,
            "urls": {
                "a": "url-a",
                "b": "url-b",
                "c": "url-c"
            },
            "revisions": []
        }
    ]
}

我正在尝试向包含其自己的 urls 字段的 revisions 数组添加一个新对象。其中两个字段应从条目的urls 复制,而最后一个字段将是新的。结果应如下所示:

{
    "id": 1,
    "entries": [
        {
            "id": 100,
            "urls": {
                "a": "url-a",
                "b": "url-b",
                "c": "url-c"
            },
            "revisions": [
                {
                    "id": 1000,
                    "urls": {
                        "a": "url-a", <-- copied
                        "b": "url-b", <-- copied
                        "c": "some-new-url" <-- new
                    }
                }
            ]
        }
    ]
}

我使用的是 MongoDB 4.2+,所以我知道我可以在更新查询中使用 $property 来引用值。但是,这似乎并没有像我预期的那样工作:

collection.updateOne(
    {
        id: 1,
        "enntries.id": 100 
    },
    {
        $push: {
            "entries.$.revisions": {
                id: 1000,
                urls: {
                    "a": "$entries.$.urls.a",
                    "b": "$entries.$.urls.b",
                    "c": "some-new-url"
                }
            } 
        }       
    }
);

元素被添加到数组中,但我看到的所有 url 值都是文字 $entries.$.urls.a。值我怀疑问题在于将引用与选择特定位置数组元素相结合。我也尝试过使用$($entries.$.urls.a),结果相同。

我怎样才能做到这一点?

【问题讨论】:

  • 您的查询几乎没有问题,所以entires 是一个数组,您想对整个数组中的每个对象执行此操作,还是对少数对象或仅对第一个对象执行此操作?
  • 我想完全为匹配entries.id 的那个做它,所以我相信我所拥有的是正确的。那部分的行为如我所愿。

标签: arrays mongodb mongodb-query aggregation-framework


【解决方案1】:

从 MongoDB 版本开始 >= 4.2 您可以在更新中使用聚合管道,这意味着您的查询的更新部分将被包裹在 [] 中,您可以在其中利用在查询中执行聚合并使用现有的字段值更新。

问题:

由于您没有在 [] 中包装更新部分来表示它是一个聚合管道,因此 .updateOne() 正在考虑将 "$entries.$.urls.a" 作为一个字符串。我相信您将无法在使用聚合管道的更新中使用$ 位置运算符。

尝试以下使用聚合管道的查询:

collection.updateOne(
  {
    id: 1,
    "entries.id": 100 /** "entries.id" is optional but much needed to avoid execution of below aggregation for doc where `id :1` but no `"entries.id": 100` */,
  } 
  [
    {
      $set: {
        entries: {
          $map: { // aggregation operator `map` iterate over array & creates new array with values.
            input: "$entries",
            in: {
              $cond: [
                { $eq: ["$$this.id", 100] }, // `$$this` is current object in array iteration, if condition is true do below functionality for that object else return same object as is to array being created.
                {
                  $mergeObjects: [
                    "$$this",
                    {
                      revisions: { $concatArrays: [ "$$this.revisions", [{ id: 1000, urls: { a: "$$this.urls.a", b: "$$this.urls.b", c: "some-new-url" } } ]] }
                    }
                  ]
                },
                "$$this" // Returning same object as condition is not met.
              ]
            }
          }
        }
      }
    }
  ]
);

$mergeObjects$$this(当前)对象中现有的revisions 字段替换为{ $concatArrays: [ "$$this.revisions", { id: 1000, urls: { a: "$$this.urls.a", b: "$$this.urls.b", c: "some-new-url" } } ] }

根据上面的字段名称revisions,由于它是一个数组,我假设该字段中有多个对象,因此我们使用$concatArrays 运算符将新对象推送到特定@ 的revisions 数组中987654337@对象。

在任何情况下,如果您的 revisions 数组字段仅包含一个对象,请将其作为对象而不是数组,或者您可以将其保留为数组并在下面的查询中使用 - 我们已删除 $concatArrays,因为我们不'不需要将新对象合并到现有的 revisions 数组中,因为我们每次只有一个对象。

collection.update(
  {
    id: 1,
    "entries.id": 100
  } 
  [
    {
      $set: {
        entries: {
          $map: {
            input: "$entries",
            in: {
              $cond: [
                { $eq: ["$$this.id", 100] },
                {
                  $mergeObjects: [
                    "$$this",
                    {
                      revisions:  [ { id: 1000, urls: { a: "$$this.urls.a", b: "$$this.urls.b", c: "some-new-url" } } ]
                    }
                  ]
                },
                "$$this"
              ]
            }
          }
        }
      }
    }
  ]
);

测试:在这里测试您的聚合管道:mongoplayground

参考: .updateOne()

注意:如果在任何情况下 .updateOne() 由于客户端或 shell 不兼容而引发错误,请使用 .update() 尝试此查询。在更新中执行聚合管道有助于节省多个数据库调用,并且对于元素数量较少的数组非常有用。

【讨论】:

  • 这是一个非常完整的答案,感谢您抽出宝贵时间。一切都完美无缺!
猜你喜欢
  • 1970-01-01
  • 2021-09-16
  • 1970-01-01
  • 2014-08-23
  • 1970-01-01
  • 2022-07-26
  • 1970-01-01
  • 2012-09-03
  • 1970-01-01
相关资源
最近更新 更多