2017-01-04 2 views
1

У меня есть коллекция с несколькими типами полей даты. Я знаю, что могу изменить их на основе их ключа, но есть ли способ найти все поля, которые имеют дату как тип, и изменить их все в одном скрипте?Найти и изменить все поля типа даты в коллекции mongodb

UPDATE

Большое спасибо chridam за помощь мне. Основываясь на его кодексе, я придумал это решение. (Примечание: У меня есть Монго 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

Можете ли вы привести пример ? – chridam

+0

@chridam Да, конечно. Представьте себе коллекцию с такими полями: loginName (string), password (string), validFrom (date), validTo (date), registerDate (date). Я бы хотел написать сценарий, который найдет все поля даты (поэтому в этом случае validFrom, validTo, registerDate) и изменяет их на timestamps/longs. Если это возможно. – S0m30n3

ответ

0

операция, как это будет включать в себя две задачи; один для получения списка полей с типом даты через MapReduce и следующего для обновления коллекции посредством агрегации или Bulk операции записи.

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: Обновление коллекции с помощью структуры агрегации

You могут использовать структуру агрегации для обновления вашей коллекции, в частности оператора $addFields, доступного в MongoDB версии 3.4 и новее. Если ваша версия сервера MongoDB не поддерживает это, вы можете обновить свою коллекцию с помощью другого обходного пути (как описано в следующем варианте).

Метки времени вычисляются с использованием арифметического оператора агрегации $subtract с датой поля в качестве уменьшаемого и дат начала эпохи 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, но вместо того, чтобы писать результаты в ту же коллекцию, вы можете получить iterate the cursor из результатов совокупности с использованием метода 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); 
+0

Большое спасибо за подробный ответ. Наконец-то я успел проверить ваш код. Это очень помогло мне, но у меня были некоторые проблемы с этим. У меня версия mongo 3.2.9, я не знаю, насколько это важно. Проблемы, которые у меня были, следующие: mr требуется параметр out. (Это может легко помочь.) isDate не определен -> поэтому я просто положил всю проверку на функцию карты. Значение не определено -> Я не знаю почему, но значение [ключ] просто не существует. Вместо этого я использовал этот ключ. – S0m30n3

+0

Я скоро обновлю свой вопрос, используя код, который я использовал, и вопрос о нем. Я был бы рад, если бы вы могли это проверить. – S0m30n3

+0

Часть «mapReduce» предназначена для работы на командной строке сервера (в оболочке mongo). Однако, если вы запустили это из приложения, вам нужно объявить «var mr, isDate» и т. Д. – chridam

Смежные вопросы