Найти и изменить все поля типа даты в коллекции mongodb
У меня есть коллекция с несколькими полями типа даты. Я знаю, что могу изменить их в зависимости от их ключа, но есть ли способ найти все поля, которые имеют тип даты, и изменить их все в одном скрипте?
ОБНОВИТЬ
Большое спасибо 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() показался лучше, чем вычитание дат.)
1 ответ
Такая операция будет включать две задачи; один, чтобы получить список полей с типом даты через MapReduce
а затем обновить коллекцию с помощью агрегации или Bulk
операции записи.
NB. Следующая методология предполагает, что все поля даты находятся на корневом уровне документа и не являются вложенными или вложенными документами.
Уменьшение карты
Первое, что вам нужно, это запустить следующее 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. Обновление коллекции с помощью структуры агрегации.
Вы можете использовать структуру агрегирования для обновления вашей коллекции, в частности $addFields
оператор доступен в MongoDB версии 3.4 и новее. Если версия сервера 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: Обновить коллекцию через Bulk
Поскольку этот вариант является обходным решением, когда $addFields
Оператор сверху не поддерживается, вы можете использовать $project
конвейер для создания новых полей меток времени с тем же $subtract
реализации, но вместо записи результатов в одну и ту же коллекцию, вы можете перебрать курсор из совокупных результатов, используя forEach()
метод и с каждым документом обновите коллекцию, используя 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);