Найти и изменить все поля типа даты в коллекции 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);
Другие вопросы по тегам