【问题标题】:Remove all fields that are null删除所有为空的字段
【发布时间】:2014-10-06 20:59:08
【问题描述】:

如何从给定集合的所有文档中删除所有 null 字段?


我有一组文档,例如:

{
    'property1': 'value1',
    'property2': 'value2',
    ...
}

但每个文档可能有一个null 条目而不是值条目。

我想通过删除所有null 条目来save disk space。在我的情况下,null 条目的存在不包含任何信息,因为我先验地知道 JSON 文档的格式。

【问题讨论】:

    标签: mongodb null


    【解决方案1】:
    // run in mongo shell  
    
    var coll = db.getCollection("collectionName");
    var cursor = coll.find();
    while (cursor.hasNext()) {
        var doc = cursor.next();
        var keys = {};
        var hasNull = false;
        for ( var x in doc) {
            if (x != "_id" && doc[x] == null) {
                keys[x] = 1;
                hasNull = true;
            }
        }
        if (hasNull) {
            coll.update({_id: doc._id}, {$unset:keys});
        }
    }
    

    【讨论】:

    • 这个答案很有帮助,但不会取消设置子文档空成员
    【解决方案2】:

    就像提到的这个问题(mongodb query without field name):

    不幸的是,MongoDB 不支持任何查询具有特定值的所有字段的方法。

    因此,您可以迭代文档(如 Wizard 的示例)或以非 mongodb 方式进行。

    如果这是一个 JSON 文件,删除 sed 中所有带有 null 的行可能会起作用:

    sed '/null/d' ./mydata.json
    

    【讨论】:

      【解决方案3】:

      您可以使用 mongo updateMany 功能,但必须通过指定要更新的参数来执行此操作,例如 year 参数:

      db.collection.updateMany({year: null}, { $unset : { year : 1 }})
      

      【讨论】:

        【解决方案4】:

        这是一个重要的问题,因为mongodb不能索引空值(即不要查询空值,否则你会等待很长时间),所以最好完全避免空值并使用setOnInsert设置默认值。

        这是一个删除空值的递归解决方案:

        /**
         * RETRIEVES A LIST OF ALL THE KEYS IN A DOCUMENT, WHERE THE VALUE IS 'NULL' OR 'UNDEFINED'
         *
         * @param doc
         * @param keyName
         * @param nullKeys
         */
        function getNullKeysRecursively(doc, keyName, nullKeys)
        {
            for (var item_property in doc)
            {
                // SKIP BASE-CLASS STUFF
                if (!doc.hasOwnProperty(item_property))
                    continue;
                // SKIP ID FIELD
                if (item_property === "_id")
                    continue;
        
                // FULL KEY NAME (FOR SUB-DOCUMENTS)
                var fullKeyName;
                if (keyName)
                    fullKeyName = keyName + "." + item_property;
                else
                    fullKeyName = item_property;
        
                // DEBUGGING
                // print("fullKeyName: " + fullKeyName);
        
                // NULL FIELDS - MODIFY THIS BLOCK TO ADD CONSTRAINTS
                if (doc[item_property] === null || doc[item_property] === undefined)
                    nullKeys[fullKeyName] = 1;
        
                // RECURSE OBJECTS / ARRAYS
                else if (doc[item_property] instanceof Object || doc[item_property] instanceof Array)
                    getNullKeysRecursively(doc[item_property], fullKeyName, nullKeys);
            }
        }
        
        /**
         * REMOVES ALL PROPERTIES WITH A VALUE OF 'NULL' OR 'UNDEFINED'.
         * TUNE THE 'LIMIT' VARIABLE TO YOUR MEMORY AVAILABILITY.
         * ONLY CLEANS DOCUMENTS THAT REQUIRE CLEANING, FOR EFFICIENCY.
         * USES bulkWrite FOR EFFICIENCY.
         *
         * @param collectionName
         */
        function removeNulls(collectionName)
        {
            var coll = db.getCollection(collectionName);
            var lastId = ObjectId("000000000000000000000000");
            var LIMIT = 10000;
            while (true)
            {
                // GET THE NEXT PAGE OF DOCUMENTS
                var page = coll.find({ _id: { $gt: lastId } }).limit(LIMIT);
                if (! page.hasNext())
                    break;
        
                // BUILD BULK OPERATION
                var arrBulkOps = [];
                page.forEach(function(item_doc)
                {
                    lastId = item_doc._id;
        
                    var nullKeys = {};
                    getNullKeysRecursively(item_doc, null, nullKeys);
        
                    // ONLY UPDATE MODIFIED DOCUMENTS
                    if (Object.keys(nullKeys).length > 0)
                    // UNSET INDIVIDUAL FIELDS, RATHER THAN REWRITE THE ENTIRE DOC
                        arrBulkOps.push(
                            { updateOne: {
                                    "filter": { _id: item_doc._id },
                                    "update": { $unset: nullKeys }
                                } }
                        );
                });
        
                coll.bulkWrite(arrBulkOps, { ordered: false } );
            }
        }
        
        // GO GO GO
        removeNulls('my_collection');
        

        之前的文档:

        {
            "_id": ObjectId("5a53ed8f6f7c4d95579cb87c"),
            "first_name": null,
            "last_name": "smith",
            "features": {
                "first": {
                    "a": 1,
                    "b": 2,
                    "c": null
                },
                "second": null,
                "third" : {},
                "fourth" : []
            },
            "other": [ 
                null, 
                123, 
                {
                    "a": 1,
                    "b": "hey",
                    "c": null
                }
            ]
        }
        

        之后的文档:

        {
            "_id" : ObjectId("5a53ed8f6f7c4d95579cb87c"),
            "last_name" : "smith",
            "features" : {
                "first" : {
                    "a" : 1,
                    "b" : 2
                }
            },
            "other" : [ 
                null, 
                123, 
                {
                    "a" : 1,
                    "b" : "hey"
                }
            ]
        }
        

        如您所见,它删除了nullundefined、空对象和空数组。如果您需要它更具侵略性/更少侵略性,只需修改“NULL FIELDS - MODIFY THIS BLOCK TO ADD CONSTRAINTS”块即可。

        欢迎编辑,尤其是@stennie

        【讨论】:

          【解决方案5】:

          Mongo 4.2开始,db.collection.update()可以接受一个聚合管道,最后允许根据其值移除一个字段:

          // { _id: ObjectId("5d0e8...d2"), property1: "value1", property2: "value2" }
          // { _id: ObjectId("5d0e8...d3"), property1: "value1", property2: null, property3: "value3" }
          db.collection.update(
            {},
            [{ $replaceWith: {
              $arrayToObject: {
                $filter: {
                  input: { $objectToArray: "$$ROOT" },
                  as: "item",
                  cond: { $ne: ["$$item.v", null] }
                }
              }
            }}],
            { multi: true }
          )
          // { _id: ObjectId("5d0e8...d2"), property1: "value1", property2: "value2" }
          // { _id: ObjectId("5d0e8...d3"), property1: "value1", property3: "value3" }
          

          详细说明:

          • 第一部分{} 是匹配查询,过滤要更新的文档(在我们的例子中是所有文档)。

          • 第二部分[{ $replaceWith: { ... }]是更新聚合管道(注意方括号表示使用聚合管道):

            • 使用$objectToArray,我们首先将文档转换为键/值数组,例如[{ k: "property1", v: "value1" }, { k: "property2", v: null }, ...]
            • 使用$filter,我们通过删除vnull 的项目来过滤此键/值数组。
            • 然后我们使用$arrayToObject 将过滤后的键/值数组转换回对象。
            • 最后,我们将修改后的文档替换为$replaceWith
          • 不要忘记{ multi: true },否则只会更新第一个匹配的文档。

          【讨论】:

          • 这应该是公认的答案。它与结构无关,只需替换集合名称即可。唯一需要注意的是,它只适用于顶级键,而不是嵌套键,如果你有的话。
          猜你喜欢
          • 1970-01-01
          • 2019-02-01
          • 1970-01-01
          • 2011-06-21
          • 1970-01-01
          • 1970-01-01
          • 2021-06-09
          • 2021-08-01
          • 2017-02-09
          相关资源
          最近更新 更多