1

複数の日付型フィールドを持つコレクションがあります。キーに基づいてそれらを変更できることはわかっていますが、タイプとして日付を持つすべてのフィールドを見つけて、それらすべてを 1 つのスクリプトで変更する方法はありますか?

アップデート

私を助けてくれた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() は日付を減算するよりも優れているように見えました。)

4

1 に答える 1

1

このような操作には 2 つのタスクが含まれます。1 つは日付型のフィールドのリストを取得するためのものでMapReduce、次は集計またはBulk書き込み操作を介してコレクションを更新するためのものです。

注意: 次の方法論では、すべての日付フィールドがドキュメントのルート レベルにあり、埋め込まれていない、またはサブドキュメントではないことを前提としています。

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: 集計フレームワークを使用してコレクションを更新する

集約フレームワークを使用して、コレクション、特に$addFieldsMongoDB バージョン 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: 一括でコレクションを更新する

このオプションは$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);
于 2017-01-04T10:02:06.747 に答える