sql >> データベース >  >> NoSQL >> MongoDB

ドキュメントのアップサートおよび/またはサブドキュメントの追加

    「アップサート」と「配列」へのアイテムの追加を混在させると、望ましくない結果が発生しやすくなるため、これを処理する方法は単純ではありません。また、配列内にある連絡先の数を示す「カウンター」などの他のフィールドをロジックで設定するかどうかによっても異なります。これらのフィールドは、アイテムが追加または削除されるたびにインクリメント/デクリメントするだけです。

    ただし、最も単純なケースでは、「連絡先」に ObjectIdなどの特異値のみが含まれている場合 別のコレクションにリンクしてから、 $ addToSet 関係する「カウンター」がない限り、修飾子は適切に機能します:

    Client.findOneAndUpdate(
        { "clientName": clientName },
        { "$addToSet": { "contacts":  contact } },
        { "upsert": true, "new": true },
        function(err,client) {
            // handle here
        }
    );
    

    そして、それはすべて問題ありません。あなたは、文書が「clientName」に一致するかどうかをテストしているだけであり、そうでない場合はそれをアップサートします。一致するかどうかに関係なく、 $ addToSet 演算子は、真に一意である任意の「オブジェクト」である、一意の「特異」値を処理します。

    困難はあなたが次のようなものを持っているところに来ます:

    { "firstName": "John", "lastName": "Smith", "age": 37 }
    

    すでに連絡先配列にあり、次のようなことをしたい場合:

    { "firstName": "John", "lastName": "Smith", "age": 38 }
    

    あなたの実際の意図がこれが「同じ」ジョン・スミスであるということであり、それは単に「年齢」が異ならないということです。理想的には、そのアレイエントリエンドネイターが新しいアレイまたは新しいドキュメントを作成するように「更新」するだけです。

    .findOneAndUpdate()でこれを操作する 更新されたドキュメントを返す場所は難しい場合があります。したがって、変更されたドキュメントに応答したくない場合は、 Bulk Operations API ここでは、MongoDBとコアドライバーが最も役立ちます。

    ステートメントを検討する:

    var bulk = Client.collection.initializeOrderedBulkOP();
    
    // First try the upsert and set the array
    bulk.find({ "clientName": clientName }).upsert().updateOne({
        "$setOnInsert": { 
            // other valid client info in here
            "contacts": [contact]
        }
    });
    
    // Try to set the array where it exists
    bulk.find({
        "clientName": clientName,
        "contacts": {
            "$elemMatch": {
                "firstName": contact.firstName,
                "lastName": contact.lastName
             }
        }
    }).updateOne({
        "$set": { "contacts.$": contact }
    });
    
    // Try to "push" the array where it does not exist
    bulk.find({
        "clientName": clientName,
        "contacts": {
            "$not": { "$elemMatch": {
                "firstName": contact.firstName,
                "lastName": contact.lastName
             }}
        }
    }).updateOne({
        "$push": { "contacts": contact }
    });
    
    bulk.execute(function(err,response) {
        // handle in here
    });
    

    ここでの一括操作は、ここでのすべてのステートメントが一度にサーバーに送信され、応答が1つしかないことを意味するため、これは便利です。また、ここでのロジックは、最大で2つの操作のみが実際に何かを変更することを意味することに注意してください。

    最初の例では、 $ setOnInsert モディファイアは、ドキュメントが一致する場合に何も変更されないようにします。ここでの唯一の変更はそのブロック内にあるため、これは「アップサート」が発生するドキュメントにのみ影響します。

    また、次の2つのステートメントでは、再度「アップサート」しようとしないことに注意してください。これは、最初のステートメントが必要な場所で成功した可能性があるか、そうでなければ問題ではなかったと見なします。

    「アップサート」がないもう1つの理由は、配列内の要素の存在をテストするために必要な条件が満たされない場合に、新しいドキュメントの「アップサート」につながるためです。これは望ましくないため、「アップサート」はありません。

    実際には、配列要素が存在するかどうかをそれぞれチェックし、既存の要素を更新するか、新しい要素を作成します。したがって、合計すると、すべての操作は、アップサートが発生した場合に「1回」または最大で「2回」変更することを意味します。可能な「2回」は、オーバーヘッドをほとんど発生させず、実際の問題は発生しません。

    また、3番目のステートメントでは、 $ not 演算子は、 $elemMatch クエリ条件を持つ配列要素が存在しないことを確認します。

    これを.findOneAndUpdate()で翻訳する もう少し問題になります。現在重要なのは「成功」だけでなく、最終的なコンテンツがどのように返されるかを決定することでもあります。

    したがって、ここでの最善のアイデアは、イベントを「シリーズ」で実行し、結果を少し魔法のように操作して、最後の「更新された」フォームを返すことです。

    ここで使用するヘルプは、両方ともasync.waterfall です。 および lodash ライブラリ:

    var _ = require('lodash');   // letting you know where _ is coming from
    
    async.waterfall(
        [
            function(callback) {
                Client.findOneAndUpdate(
                   { "clientName": clientName },
                   {
                      "$setOnInsert": { 
                          // other valid client info in here
                          "contacts": [contact]
                      }
                   },
                   { "upsert": true, "new": true },
                   callback
                );
            },
            function(client,callback) {
                Client.findOneAndUpdate(
                    {
                        "clientName": clientName,
                        "contacts": {
                           "$elemMatch": {
                               "firstName": contact.firstName,
                               "lastName": contact.lastName
                           }
                        }
                    },
                    { "$set": { "contacts.$": contact } },
                    { "new": true },
                    function(err,newClient) {
                        client = client || {};
                        newClient = newClient || {};
                        client = _.merge(client,newClient);
                        callback(err,client);
                    }
                );
            },
            function(client,callback) {
                Client.findOneAndUpdate(
                    {
                        "clientName": clientName,
                        "contacts": {
                           "$not": { "$elemMatch": {
                               "firstName": contact.firstName,
                               "lastName": contact.lastName
                           }}
                        }
                    },
                    { "$push": { "contacts": contact } },
                    { "new": true },
                    function(err,newClient) {
                        newClient = newClient || {};
                        client = _.merge(client,newClient);
                        callback(err,client);
                    }
                );
            }
        ],
        function(err,client) {
            if (err) throw err;
            console.log(client);
        }
    );
    

    これは以前と同じロジックに従い、これらのステートメントの2つまたは1つだけが実際に何かを実行し、返される「新しい」ドキュメントが nullになる可能性があります。 。ここでの「ウォーターフォール」は、各ステージからの結果を次のステージに渡します。これには、エラーがすぐに分岐する終了も含まれます。

    この場合、 null 空のオブジェクトと交換されます{} および _。merge() メソッドは、後の各段階で2つのオブジェクトを1つに結合します。これにより、先行するどの操作が実際に何かを実行したかに関係なく、変更されたオブジェクトである最終結果が得られます。

    もちろん、 $ pullには別の操作が必要になります 、また、質問には、それ自体がオブジェクトフォームとして入力データが含まれています。しかし、それらは実際にはそれ自体が答えです。

    これにより、少なくとも更新パターンにアプローチする方法を開始できます。



    1. WaterlineとSails.js(バージョン0.10)を使用してmongoデータベースから個別の値を抽出するにはどうすればよいですか?

    2. Birtレポートの左側の結合で重複する行を取得する

    3. mongodbTTLがドキュメントを削除しない

    4. Python 3.2用のPyMongoに相当するものはありますか?