MongoDB重命名数组中的数据库字段

56

我需要重命名这个identifier

{ "general" : 
  { "files" : 
    { "file" : 
      [  
        {  "version" : 
          {  "software_program" : "MonkeyPlus",      
             "indentifier" : "6.0.0" 
          } 
        } 
      ] 
    } 
  } 
}

我已经尝试过。

db.nrel.component.update(
  {},
  { $rename: {
    "general.files.file.$.version.indentifier" : "general.files.file.$.version.identifier"
  } },
  false, true
)

但它返回:$rename 源可能不是动态数组。


1
$rename 不会展开数组,文档 - Alexander Azarov
@Alexander Azarov,有没有解决这个问题的想法?我听说过有人复制到可以进行$rename操作的字段中… - Andrew Samuelsen
就个人而言,我正在编写脚本来遍历集合并进行迁移。 - Alexander Azarov
如果您正在尝试使用数据库命令完成此操作:如何使用数据库命令重命名数组中的字段? - leonheess
8个回答

56

说实话,虽然听起来做起来很糟糕,但解决方案实际上相当容易。当然,这取决于你有多少条记录。以下是我的例子:

db.Setting.find({ 'Value.Tiers.0.AssetsUnderManagement': { $exists: 1 } }).snapshot().forEach(function(item)
{    
    for(i = 0; i != item.Value.Tiers.length; ++i)
    {
        item.Value.Tiers[i].Aum = item.Value.Tiers[i].AssetsUnderManagement;
        delete item.Value.Tiers[i].AssetsUnderManagement;
    }
    
    db.Setting.update({_id: item._id}, item);
});

我遍历我的集合,找到包含错误名称的数组。然后,我遍历子集合,设置新值,删除旧值,并更新整个文档。这相对来说是比较轻松的。虽然我只需要搜索几万行数据中的几十条符合条件的数据。

希望这个答案能够帮助到其他人!

编辑: 在查询中添加snapshot()。请查看评论以了解原因。

在从数据库检索任何文档之前,必须在光标上应用snapshot()。您只能在未分片的集合中使用snapshot()

从MongoDB 3.4开始,snapshot()函数已被移除。因此,如果使用Mongo 3.4+,上面的示例应该删除snapshot()函数。


3
我认为这应该成为被接受的答案。我将此方法应用于多个包含多达1200万个文档的集合中,效果非常好! - Sander Toonen
2
当然可以。这个方法非常好用。可惜MongoDB目前还不能原生地包含这种行为。 - Faliorn
2
最好使用snapshot(): db.Setting.find({ 'Value.Tiers.0.AssetsUnderManagement': { $exists: 1 } }).snapshot().forEach(...) 否则,您可能会陷入无限循环中。 另请参阅https://docs.mongodb.org/v3.0/reference/method/cursor.snapshot/ - Patrycja K
对我来说是新的。感谢您提供的详细信息,@PatrycjaK!我会更新我的答案。 - Eli Gassert
@Andrew Samuelsen 我认为这应该是被选中的答案 - 一个脚本来解决“按设计”问题。 - NoamG
@PatrycjaK:snapshot在MongoDB 4.0中已被弃用。 - Dan Dascalescu

26
根据文档中提到的,没有办法通过单个命令直接重命名数组中的字段。你唯一的选择是遍历集合文档,读取它们并使用$unset旧值/$set新值操作来更新每个文档。

37
被 MongoDB 卡住了...这种事情让我感到难过 :( - Shannon
我不反对。我想这应该是比较容易实现的事情。 - Remon van Vliet
这个答案已经过时了,请查看 Sudhesh 的答案,我认为那是最好的。 - Amit Beckenstein

21

我遇到了类似的问题。在我的情况下,我发现以下方法更加简单:

  1. 将集合导出为json文件:
mongoexport --db mydb --collection modules --out modules.json
  1. 我使用我喜欢的文本编辑工具进行了查找和替换JSON。

  2. 在此过程中,我重新导入了编辑后的文件,并删除了旧的集合:

mongoimport --db mydb --collection modules --drop --file modules.json

3
我有点怀疑这种方法在大型数据集上的有效性。比如说,如果我的数据库有5GB,这个过程听起来会很慢。或者可能是我没有使用正确的文本编辑工具 =-} - Matthew Bonig
3
请注意现有数据库。如果在执行这些步骤的过程中添加/修改了记录,则更改将会丢失。 - Eli Gassert
如果你只是在自己的笔记本电脑上运行某些东西,那么这还可以接受,但如果你在生产环境中工作,这就不是一个可行的解决方案。 - Valerie R. Coffman

20

Mongo 4.2 开始,db.collection.update() 可以接受聚合管道,最终允许基于其自身值更新字段:

// { general: { files: { file: [
//   { version: { software_program: "MonkeyPlus", indentifier: "6.0.0" } }
// ] } } }
db.collection.updateMany(
  {},
  [{ $set: { "general.files.file": {
       $map: {
         input: "$general.files.file",
         as: "file",
         in: {
           version: {
             software_program: "$$file.version.software_program",
             identifier: "$$file.version.indentifier" // fixing the typo here
           }
         }
       }
  }}}]
)
// { general: { files: { file: [
//   { version: { software_program: "MonkeyPlus", identifier: "6.0.0" } }
// ] } } }
字面上来说,这个操作通过将"general.files.file"数组$map映射到一个包含相同"software_program"字段和重命名的"identifier"字段的"version"对象中,并在其中re$set "indentifier"字段的值。需要注意以下几点:
  • 第一部分{}是匹配查询,过滤要更新的文档(在本例中为所有文档)。
  • 第二部分[{$set:{"general.files.file":{...}}}]是更新聚合管道(注意方括号表示使用聚合管道):
    • $set是一种新的聚合操作器,本例中替换了"general.files.file"数组的值。
    • 使用$map操作,我们通过基本相同的元素,但具有"identifier"字段而不是"indentifier",替换"general.files.file"数组的所有元素。
    • input是要映射的数组。
    • as是赋给循环元素的变量名称。
    • in是实际应用于元素的转换。在这种情况下,它通过提取其先前值使用$$file.xxxx记号(其中file是从as部分给出的元素名称)将元素替换为一个"version"对象组成"software_program""identifier"字段。

2
你一直在手动维护 software_program。 但是如果版本字段有很多子字段,并且每个文档中可能有不同的字段呢? - Haidang Nguyen
回答@HaidangNguyen的问题,以及一般的好习惯 - 在$map的in中使用$mergeObjects :) 对于上面的示例,可以这样写:in: { $mergeObjects: ['$$file', / * 这里是更改 */] } - Amit Beckenstein

11

我不得不面对同样的模式问题。因此,这个查询将对想要重命名嵌入式数组中字段的人有所帮助。

db.getCollection("sampledocument").updateMany({}, [
  {
    $set: {
      "general.files.file": {
        $map: {
          input: "$general.files.file",
          in: {
            version: {
              $mergeObjects: [
                "$$this.version",
                { identifer: "$$this.version.indentifier" },
              ],
            },
          },
        },
      },
    },
  },
  { $unset: "general.files.file.version.indentifier" },
]);

另一种解决方案


6
我想重命名数组中的一个属性,我已经使用了这个方法。
db.getCollection('YourCollectionName').find({}).snapshot().forEach(function(a){
    a.Array1.forEach(function(b){
        b.Array2.forEach(function(c){
            c.NewPropertyName = c.OldPropertyName;
            delete c["OldPropertyName"];                   
        });
    });
    db.getCollection('YourCollectionName').save(a)  
});

3
自 MongoDB 4.0 开始,snapshot 已经被弃用。 - Dan Dascalescu
另外,您可以添加if(b.hasOwnProperty("xxx"))来验证属性或数组是否存在。 - MonkeyDLuffy

2
使用聚合(Mongo 4.0+)是最简单、最短的解决方案。
db.myCollection.aggregate([
  {
    $addFields: {
      "myArray.newField": {$arrayElemAt: ["$myArray.oldField", 0] }
    }
  },
  {$project: { "myArray.oldField": false}},
  {$out: {db: "myDb", coll: "myCollection"}}
])

如上所述,使用forEach循环的问题在于当集合非常庞大时性能非常差。


1

My proposal would be this one:

db.nrel.component.aggregate([
   { $unwind: "$general.files.file" },
   {
      $set: {
         "general.files.file.version.identifier": {
            $ifNull: ["$general.files.file.version.indentifier", "$general.files.file.version.identifier"]
         }
      }
   },
   { $unset: "general.files.file.version.indentifier" },
   { $set: { "general.files.file": ["$general.files.file"] } },
   { $out: "nrel.component" } // carefully - it replaces entire collection.
])

然而,这仅适用于数组 general.files.file 仅有一个文档的情况。很可能这并不总是成立,那么你可以使用这个:
db.nrel.componen.aggregate([
   { $unwind: "$general.files.file" },
   {
      $set: {
         "general.files.file.version.identifier": {
            $ifNull: ["$general.files.file.version.indentifier", "$general.files.file.version.identifier"]
         }
      }
   },
   { $unset: "general.files.file.version.indentifier" },
   { $group: { _id: "$_id", general_new: { $addToSet: "$general.files.file" } } },
   { $set: { "general.files.file": "$general_new" } },
   { $unset: "general_new" },
   { $out: "nrel.component" } // carefully - it replaces entire collection.
])

对于这么简单的任务来说,太复杂了。 - BabyishTank

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接