首页 > 解决方案 > 更新中数组中位置元素的参考值

问题描述

假设我有一个看起来像这样的文档:

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

我正在尝试将一个新对象添加到revisions包含其自己的urls字段的数组中。应该从条目的 复制两个字段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),结果相同。

我怎样才能使这项工作?

标签: arraysmongodbmongodb-queryaggregation-framework

解决方案


从 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将(当前)对象revisions中的现有字段替换为.$$this{ $concatArrays: [ "$$this.revisions", { id: 1000, urls: { a: "$$this.urls.a", b: "$$this.urls.b", c: "some-new-url" } } ] }

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

在任何情况下,如果您的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(). 在更新中执行聚合管道有助于节省多个数据库调用,并且对于元素数量较少的数组非常有用。


推荐阅读