查找并更改 mongodb 集合中的所有日期类型字段

Find and change all date type fields in mongodb collection

我有一个包含多个日期类型字段的集合。我知道我可以根据它们的键更改它们,但是有没有一种方法可以找到所有类型为日期的字段并在一个脚本中更改所有字段?

更新

非常感谢 chridam 帮助我。根据他的代码,我想出了这个解决方案。 (注意:我有 mongo 3.2.9,chridam 的回答中的一些代码片段不会 运行。它可能有效,但对我不起作用。)

map = function() {
    for (var key in this) { 
        if (key != null && this[key] != null && this[key] instanceof Date){ 
            emit(key, null); 
        }
    }
}

collectionName = "testcollection_copy";

mr = db.runCommand({
    "mapreduce": collectionName,
    "map": map,  
    "reduce": function() {},
    "out": "map_reduce_test" // out is required
}) 

dateFields = db[mr.result].distinct("_id")
printjson(dateFields)

//updating documents
db[collectionName].find().forEach(function (document){
   for(var i=0;i<dateFields.length;i++){
       document[dateFields[i]] = new NumberLong(document[dateFields[i]].getTime());
   } 
   db[collectionName].save(document);
});

由于投影不起作用,我使用上面的代码来更新文档。 我唯一的问题是为什么要使用 bulkWrite?

(此外,getTime() 似乎比减去日期更好。)

像这样的操作将涉及两个任务;一个通过 MapReduce and the next to update the collection via aggregation or Bulk 写操作获取日期类型的字段列表。

注意:以下方法假定所有日期字段都位于文档的根级别,而不是嵌入文档或子文档。

MapReduce

首先需要运行下面的mapReduce操作。这将帮助您确定 collection 中每个文档的每个 属性 是否属于日期类型,并且 returns 是否是不同的日期字段列表:

// define helper function to determine if a key is of Date type
isDate = function(dt) {
    return dt && dt instanceof Date && !isNaN(dt.valueOf());
}

// map function
map = function() {
    for (var key in this) { 
        if (isDate(value[key]) 
            emit(key, null); 
    }
}

// variable with collection name
collectionName = "yourCollectionName";

mr = db.runCommand({
    "mapreduce": collectionName,
    "map": map,  
    "reduce": function() {}
}) 

dateFields = db[mr.result].distinct("_id")
printjson(dateFields)

//output: [ "validFrom", "validTo", "registerDate"" ]

选项 1:通过聚合框架更新 collection

您可以使用聚合框架来更新您的 collection,特别是 $addFields 运算符在 MongoDB 版本 3.4 和更高版本中可用.如果您的 MongoDB 服务器版本不支持此功能,您可以使用其他解决方法更新 collection(如下一个选项所述)。

时间戳是使用$subtract算术聚合运算符计算得到的,日期字段为被减数,纪元以来的日期new Date("1970-01-01")为减数。

聚合管道的结果文档然后通过 $out 运算符写入相同的 collection,从而更新 collection新领域。

本质上,您希望最终 运行 使用上述算法将日期字段转换为时间戳的以下聚合管道:

pipeline = [
    {
        "$addFields": {
            "validFrom": { "$subtract": [ "$validFrom", new Date("1970-01-01") ] },
            "validTo": { "$subtract": [ "$validTo", new Date("1970-01-01") ] },
            "registerDate": { "$subtract": [ "$registerDate", new Date("1970-01-01") ] }
        }
    },
    { "$out": collectionName }
]
db[collectionName].aggregate(pipeline)

您可以在给定日期字段列表的情况下动态创建上述管道数组,如下所示:

var addFields = { "$addFields": { } },
    output = { "$out": collectionName };

dateFields.forEach(function(key){
    var subtr = ["$"+key, new Date("1970-01-01")];
    addFields["$addFields"][key] = { "$subtract": subtr };
});

db[collectionName].aggregate([addFields, output])

选项 2:通过批量更新 collection

由于此选项是不支持上述 $addFields 运算符的解决方法,因此您可以使用 $project 管道以使用相同的 $subtract 实现创建新的时间戳字段,而不是将结果写入相同的 collection,您可以使用 forEach() 方法从聚合结果中 迭代游标 对于每个文档,使用 bulkWrite() 方法更新 collection。

以下示例展示了这种方法:

ops = []
pipeline = [
    {
        "$project": {
            "validFrom": { "$subtract": [ "$validFrom", new Date("1970-01-01") ] },
            "validTo": { "$subtract": [ "$validTo", new Date("1970-01-01") ] },
            "registerDate": { "$subtract": [ "$registerDate", new Date("1970-01-01") ] }
        }
    }
]

db[collectionName].aggregate(pipeline).forEach(function(doc) {
    ops.push({
        "updateOne": {
            "filter": { "_id": doc._id },
            "update": {
                "$set": { 
                    "validFrom": doc.validFrom,
                    "validTo": doc.validTo,
                    "registerDate": doc.registerDate
                }
            }
        }
    });

    if (ops.length === 500 ) {
        db[collectionName].bulkWrite(ops);
        ops = [];
    }
})

if (ops.length > 0)  
    db[collectionName].bulkWrite(ops);

使用与上述选项 1 相同的方法动态创建管道和批量方法 objects:

var ops = [],
    project = { "$project": { } },

dateFields.forEach(function(key){
    var subtr = ["$"+key, new Date("1970-01-01")];
    project["$project"][key] = { "$subtract": subtr };
});

setDocFields = function(doc, keysList) { 
    setObj = { "$set": { } };
    return keysList.reduce(function(obj, key) {  
        obj["$set"][key] = doc[key];
        return obj;
    }, setObj )
}

db[collectionName].aggregate([project]).forEach(function(doc) {
    ops.push({
        "updateOne": {
            "filter": { "_id": doc._id },
            "update": setDocFields(doc, dateFields)
        }
    });

    if (ops.length === 500 ) {
        db[collectionName].bulkWrite(ops);
        ops = [];
    }
})

if (ops.length > 0)  
    db[collectionName].bulkWrite(ops);