我有一个包含多个日期类型字段的集合。我知道我可以根据他们的密钥更改它们,但有没有办法找到所有具有日期类型的字段并在一个脚本中更改所有字段?
更新
非常感谢 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()似乎比减去日期要好。)
答案 0 :(得分:0)
这样的操作将涉及两项任务;一个通过 MapReduce
获取日期类型的字段列表,然后通过聚合或 Bulk
写操作更新集合。< / p>
NB :以下方法假设所有日期字段都位于文档的根级别,而不是嵌入式或子文档。
<强>的MapReduce 强>
您需要做的第一件事就是运行以下 mapReduce
操作。这将帮助您确定集合中每个文档的每个属性是否具有日期类型,并返回日期字段的不同列表:
// 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:通过汇总框架更新集合
您可以使用聚合框架来更新您的收藏集,尤其是MongoDB 3.4及更高版本中提供的 $addFields
运算符。如果您的MongoDB服务器版本不支持此功能,您可以使用其他解决方法更新您的集合(如下一个选项中所述)。
时间戳是使用 $subtract
算术聚合运算符计算的,其中日期字段为minuend,自纪元new Date("1970-01-01")
以来的日期为减数。
然后,通过 $out
运算符将聚合管道的结果文档写入同一集合,从而使用新字段更新集合。
实质上,您希望最终运行以下聚合管道,该管道使用上述算法将日期字段转换为时间戳:
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:通过批量更新集合
由于此选项是不支持上述 $addFields
运算符的解决方法,您可以使用 $project
管道创建新的时间戳字段具有相同的 $subtract
实现,但不是将结果写入同一个集合,您可以使用 iterate the cursor <从汇总结果中forEach()
/ strong>方法和每个文档,使用 bulkWrite()
方法更新集合。
以下示例显示了这种方法:
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 相同的方法动态创建管道和批量方法对象:
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);