删除所有空字段

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

我有一组文档,例如:

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

但是每个文档可能会有一个null条目而不是值条目。
我想通过删除所有null条目来节省磁盘空间。在我的情况下,存在null条目不包含任何信息,因为我事先知道JSON文档的格式。
6个回答

15

从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 },否则只会更新第一个匹配的文档。


2
这应该是被接受的答案。它不依赖于数据结构,并且通过简单地替换集合名称来实现。唯一的注意事项是,它仅适用于顶层键,如果您有嵌套的键,则无法使用。 - sandboxbohemian

12
// 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});
    }
}

这个答案很有帮助,但不会取消子文档中的空成员。 - mils

3
您可以使用Mongo的updateMany功能,但必须通过指定要更新的参数(例如year参数)来执行此操作:
db.collection.updateMany({year: null}, { $unset : { year : 1 }})

3
这是一个重要的问题,因为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。


1

就像这个问题所提到的一样 (mongodb查询没有字段名称):

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

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

如果这是一个JSON文件,则在sed中删除所有带null的行可能有效:

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

0
2022年更新: 如果您从数据库中删除具有值Null[]""{}的键,那么它不会减小磁盘上的大小。 您需要在上传数据到集合之前执行此操作。
我亲自测试过。我的集合中有6,000,000个文档。运行了Xavier Guihot的脚本之前,它的大小为7.8GB,运行后变成了7.9GB。 我确认,该脚本可以完成工作并删除键,只是它不会减少数据库空间分配的大小。
然后我完全删除了集合,并导入了已经格式化(删除了所有具有值Null[]""{}的键)的.json转储文件。集合大小为6.1GB。这比原始大小减少了22%。
这是一个Python脚本,我用它来从JSON转储中删除所有空键:
import fileinput
import json

for line in fileinput.input(inplace=1):
    j = {k:v for k, v in json.loads(line).items() if v}
    print(line.replace(line, json.dumps(j)))

只需使用文件名作为参数运行脚本,例如:python3 main.py dump-00001


注意:在对数据库进行更改后,需要等待约200秒钟,因为WiredTiger会保留数据的历史备份以确保一致性。这意味着只有在200秒后,您才能看到实际的数据库存储分配情况。200秒是该操作的默认值。


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