如何在深度嵌套的 MongoDB 文档中保存删除

How to save deletion in a deeply nested MongoDB document

我是 MongoDB 的新手,我正在使用 MongoDB shell 来执行这些操作。 我正在努力从所有项目中删除名为 Process 的数组,但似乎我没有正确理解删除概念。 我们使用的文档嵌套很深——我们不知道有多少项,也不知道嵌套的层次有多深。
到目前为止我尝试的是使用递归来遍历项目:



    function removeAllProcessFields(docItems)
    {
        if(Array.isArray(docItems))
        {
            docItems.forEach(function(item)
                {
                   print("idItem: "+item._id);
                   if(item.Process == null)
                   {
                      print("Process null");
                   }
                   else
                   {
                      $unset: { Process: ""}
                   }
                   removeAllProcessFields(item.Items);
            })
        }
    }
    
    var docs = db.getCollection('MyCollection').find({})
    docs.forEach(function(doc)
    {
        print("idDoc: "+doc._id);
        removeAllProcessFields(doc.Items);
    })

但是我很难正确使用unset来保存操作。
示例文档为:



    {
        "_id": "622226d319517e83e8ed6151",
        "Name": "test1",
        "Description": "",
        "Items": [{
            "_id": "622226d319517e83e8ed614e",
            "Name": "test-item",
            "Description": "",
            "Process": [{
                "Name": "Step1"
            }, {
                "Name": "Step2"
            }],
            "Items": [{
                    "_id": "622226d319517e83e8ed614f",
                    "Name": "test-subItem1",
                    "Description": "",
                    "Process": [{
                        "Name": "StepSub1"
                    }, {
                        "Name": "StepSub2"
                    }, {
                        "Name": "StepSub3"
                    }],
                    "Items": []
                },
                {
                    "_id": "622226d319517e83e8ed6150",
                    "Name": "test-subItem2",
                    "Description": "",
                    "Process": [{
                        "Name": "StepSub4"
                    }, {
                        "Name": "StepSub5"
                    }, {
                        "Name": "StepSub6"
                    }],
                    "Items": []
                }
    
            ]
        }]
    }

我希望达到的目标是:



    {
        "_id": "622226d319517e83e8ed6151",
        "Name": "test1",
        "Description": "",
        "Items": [{
            "_id": "622226d319517e83e8ed614e",
            "Name": "test-item",
            "Description": "",
            "Items": [{
                    "_id": "622226d319517e83e8ed614f",
                    "Name": "test-subItem1",
                    "Description": "",
                    "Items": []
                },
                {
                    "_id": "622226d319517e83e8ed6150",
                    "Name": "test-subItem2",
                    "Description": "",
                    "Items": []
                }
    
            ]
        }]
    }

可能会使用 $[] 位置运算符:

db.collection.update({},
{
 $unset: {
  "Items.$[].Items.$[].Process": 1,
  "Items.$[].Process": 1
}
})

你只需要在递归中构造它...

playground

JavaScript 递归函数示例:

 mongos> db.rec.find()
{ "_id" : ObjectId("622a6c46ae295edb276df8e2"), "Items" : [ { "a" : 1 }, { "Items" : [ { "Items" : [ { "Items" : [ ], "Process" : [ 1, 2, 3 ] } ], "Process" : [ 4, 5, 6 ] } ], "Process" : [ ] } ] }

 mongos> db.rec.find().forEach(function(obj){ var id=obj._id,ar=[],z=""; function x(obj){ if(typeof obj.Items != "undefined" ){ obj.Items.forEach(function(k){ if( typeof k.Process !="undefined" ){ z=z+".Items.$[]";ar.push(z.substring(1)+".Process") }; if(typeof k.Items != "undefined"){x(k)}else{}  }) }else{}  };x(obj);ar.forEach(function(del){print( "db.collection.update({_id:ObjectId('"+id+"')},{$unset:{'"+del+"':1}})" );}) })

 db.collection.update({_id:ObjectId('622a6c46ae295edb276df8e2')},{$unset:{'Items.$[].Process':1}})
 db.collection.update({_id:ObjectId('622a6c46ae295edb276df8e2')},{$unset:{'Items.$[].Items.$[].Process':1}})
 db.collection.update({_id:ObjectId('622a6c46ae295edb276df8e2')},{$unset:{'Items.$[].Items.$[].Items.$[].Process':1}})
 mongos> 

解释:

  1. 使用 forEach 遍历集合中的所有文档
  2. 定义递归函数 x 将循环遍历任意数量的嵌套项目并确定是否有 Process 字段并推送到数组 ar
  3. 最后遍历数组 ar 并构造更新 $unset 查询,在示例中仅为安全而打印,但您可以改进为每个文档生成单个查询并执行未设置的查询...

假设您使用的是 v>=4.4,您可以使用 $merge 的“合并到自身”功能,再加上定义一个递归函数来扫描整个集合,并通过外科手术删除任何一个或一系列字段层次结构的级别。在处理也是任意分层的 json-schema 数据时,会出现同样的需求。

下面的解决方案有额外的逻辑来“标记”有任何修改的文档,因此 others 可以从传递给 $merge 的更新集中删除。它还可以进一步细化以减少一些变量;它是从一个更通用的解决方案中编辑下来的,该解决方案必须检查键 值。

db.foo.aggregate([
    {$replaceRoot: {newRoot: {$function: {
        body: function(obj, target) {
            var didSomething = false;

            var process = function(holder, spot, value) {
                // test FIRST since [] instanceof Object is true!                           
                if(Array.isArray(value)) {
                    for(var jj = value.length - 1; jj >= 0; jj--) {
                        process(value, jj, value[jj]);
                    }
                } else if(value instanceof Object) {
                    walkObj(value);
                }
            };

            var walkObj = function(obj) {
                Object.keys(obj).forEach(function(k) {
                    if(target.indexOf(k) > -1) {
                        delete obj[k];
                        didSomething = true;
                    } else {
                        process(obj, k, obj[k]);
                    }
                });
            }

            // ENTRY POINT:      
            if(!Array.isArray(target)) {
                target = [ target ]; // if not array, make it an array
            }
            walkObj(obj);

            if(!didSomething) {
                obj['__didNothing'] = true;
            }

            return obj;
        },

        // Invoke!
        // You can delete multiple fields with an array, e.g.:
        //   ..., ['Process','Description']
        args: [ "$$ROOT", 'Process' ],

        lang: "js"
        }}
    }}

    // Only let thru docs WITHOUT the marker:
    ,{$match: {'__didNothing':{$exists:false}} }

    ,{$merge: {
        into: "foo",
        on: [ "_id" ],
        whenMatched: "merge",
        whenNotMatched: "fail"
    }}

]);