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

Mongodbで一致するサブドキュメントの配列を一括更新します

    最短の答えでは、「はい」と「いいえ」の両方です。

    実際には「複数の」arrayFiltersを提供できるため、個々の配列要素を照合し、それらを1つのステートメントで個別の値で更新する方法が実際にあります。 条件を設定し、更新ステートメントでそれらの識別子を使用します。

    ここでの特定のサンプルの問題は、「変更セット」のエントリの1つ(最後のエントリ)が、現在存在するどの配列メンバーとも実際には一致しないことです。ここでの「推定」アクションは、 $ push 一致しなかった新しいメンバーが、見つからなかった配列に追加されました。ただし、その特定のアクションはできません 「単一操作」で実行する 、ただし、 bullkWrite()> そのケースをカバーするために「複数の」ステートメントを発行します。

    異なるアレイ条件のマッチング

    ポイントでそれを説明して、あなたの「チェンジセット」の最初の2つのアイテムを考えてください。 「シングル」を適用できます 複数のarrayFiltersでステートメントを更新します このように:

    db.avail_rates_copy.updateOne(
      { "_id": 12345 },
      { 
        "$set": {
          "rates.$[one]": {
            "productId" : NumberInt(1234), 
            "rate" : 400.0, 
            "rateCardId": NumberInt(1),
            "month" : NumberInt(201801)
          },
          "rates.$[two]": {
            "productId" : NumberInt(1234), 
            "rate" : 500.0, 
            "rateCardId": NumberInt(1),
            "month" : NumberInt(201802)
          } 
        }
      },
      { 
        "arrayFilters": [
          {
            "one.productId": NumberInt(1234),
            "one.rateCardId": NumberInt(1),
            "one.month": NumberInt(201801)
          },
          {
            "two.productId": NumberInt(1234),
            "two.rateCardId": NumberInt(1),
            "two.month": NumberInt(201802)
          }
        ]
      }
    )
    

    実行すると、変更されたドキュメントは次のようになります。

    {
            "_id" : 12345,
            "_class" : "com.example.ProductRates",
            "rates" : [
                    {                             // Matched and changed this by one
                            "productId" : 1234,
                            "rate" : 400,
                            "rateCardId" : 1,
                            "month" : 201801
                    },
                    {                            // And this as two
                            "productId" : 1234,
                            "rate" : 500,
                            "rateCardId" : 1,
                            "month" : 201802
                    },
                    {
                            "productId" : 1234,
                            "rate" : 400,
                            "rateCardId" : 2,
                            "month" : 201803
                    },
                    {
                            "productId" : 1235,
                            "rate" : 500,
                            "rateCardId" : 1,
                            "month" : 201801
                    },
                    {
                            "productId" : 1235,
                            "rate" : 234,
                            "rateCardId" : 2,
                            "month" : 201803
                    }
            ]
    }
    

    ここで、 arrayFiltersのリスト内で各「識別子」を指定することに注意してください。 次のように要素に一致する複数の条件を使用します:

      {
        "one.productId": NumberInt(1234),
        "one.rateCardId": NumberInt(1),
        "one.month": NumberInt(201801)
      },
    

    したがって、各「条件」は効果的に次のようにマッピングされます。

      <identifier>.<property>
    

    したがって、 "rates"を見ていることがわかります。 $ []

     "rates.$[one]"
    

    そして、 "rates"の各要素を調べます 条件に合うように。つまり、 "one" 識別子は、接頭辞 "one"の条件に一致します 同様に、接頭辞 "two"が付いた他の一連の条件についても同様です。 したがって、実際の更新ステートメントは、識別子に割り当てられた条件に一致するステートメントにのみ適用されます。

    "rates"が必要な場合 オブジェクト全体ではなくプロパティの場合は、次のように表記します。

    { "$set": { "rates.$[one].rate": 400, "rates.$[two].rate": 500 } }
    

    一致しないオブジェクトの追加

    したがって、最初の部分は比較的簡単に理解できますが、前述のように、 $ push 「存在しない要素」については別の問題です。配列要素が「欠落している」と判断するには、基本的に「ドキュメント」レベルのクエリ条件が必要だからです。

    これが本質的に意味するのは、 $ push 各配列要素を探して、それが存在するかどうかを確認します。存在しない場合、ドキュメントは一致し、 $ push 実行されます。

    ここで、 bullkWrite() が機能し、「変更セット」内のすべての要素について、上記の最初の操作に追加の更新を追加することで使用します。

    db.avail_rates_copy.bulkWrite(
      [
        { "updateOne": {
          "filter": { "_id": 12345 },
          "update": {
            "$set": {
              "rates.$[one]": {
                "productId" : NumberInt(1234), 
                "rate" : 400.0, 
                "rateCardId": NumberInt(1),
                "month" : NumberInt(201801)
              },
              "rates.$[two]": {
                "productId" : NumberInt(1234), 
                "rate" : 500.0, 
                "rateCardId": NumberInt(1),
                "month" : NumberInt(201802)
              },
              "rates.$[three]": {
                "productId" : NumberInt(1235), 
                "rate" : 700.0, 
                "rateCardId": NumberInt(1),
                "month" : NumberInt(201802)
              }
            }
          },
          "arrayFilters": [
            {
              "one.productId": NumberInt(1234),
              "one.rateCardId": NumberInt(1),
              "one.month": NumberInt(201801)
            },
            {
              "two.productId": NumberInt(1234),
              "two.rateCardId": NumberInt(1),
              "two.month": NumberInt(201802)
            },
            {
              "three.productId": NumberInt(1235),
              "three.rateCardId": NumberInt(1),
              "three.month": NumberInt(201802)
            }
          ]    
        }},
        { "updateOne": {
          "filter": {
            "_id": 12345,
            "rates": {
              "$not": {
                "$elemMatch": {
                  "productId" : NumberInt(1234), 
                  "rateCardId": NumberInt(1),
                  "month" : NumberInt(201801)
                }
              }
            }
          },
          "update": {
            "$push": {
              "rates": {
                "productId" : NumberInt(1234), 
                "rate" : 400.0, 
                "rateCardId": NumberInt(1),
                "month" : NumberInt(201801)
              }
            }
          }
        }},
        { "updateOne": {
          "filter": {
            "_id": 12345,
            "rates": {
              "$not": {
                "$elemMatch": {
                  "productId" : NumberInt(1234), 
                  "rateCardId": NumberInt(1),
                  "month" : NumberInt(201802)
                }
              }
            }
          },
          "update": {
            "$push": {
              "rates": {
                "productId" : NumberInt(1234), 
                "rate" : 500.0, 
                "rateCardId": NumberInt(1),
                "month" : NumberInt(201802)
              }
            }
          }
        }},
        { "updateOne": {
          "filter": {
            "_id": 12345,
            "rates": {
              "$not": {
                "$elemMatch": {
                  "productId" : NumberInt(1235),
                  "rateCardId": NumberInt(1),
                  "month" : NumberInt(201802)
                }
              }
            }
          },
          "update": {
            "$push": {
              "rates": {
                "productId" : NumberInt(1235),
                "rate" : 700.0, 
                "rateCardId": NumberInt(1),
                "month" : NumberInt(201802)
              }
            }
          }
        }}
      ],
      { "ordered": true }
    )
    

    $ elemMatchに注意してください これは「複数の条件」によって配列要素を一致させるための要件であるため、クエリフィルターを使用します。 arrayFiltersでは必要ありませんでした のみであるためエントリ すでに適用されている各配列項目を確認しますが、「クエリ」として、条件には $ elemMatch 単純な「ドット表記」は誤った一致を返すためです。

    $ notも参照してください。 ここでは、演算子を使用して $ elemMatch 、私たちの本当の条件は、「配列要素と一致しない」ドキュメントのみと一致することです。 提供された条件に加えて、それが新しい要素を追加するための選択を正当化するものです。

    そして、サーバーに発行されたその単一のステートメントは、基本的に4つを試行します 一致した配列要素の更新を試行するための操作と、3つごとの更新操作 $ pushを試みる「変更セット」 ドキュメントが「変更セット」の配列要素の条件と一致しないことが判明した場合。

    したがって、結果は期待どおりです。

    {
            "_id" : 12345,
            "_class" : "com.example.ProductRates",
            "rates" : [
                    {                               // matched and updated
                            "productId" : 1234,
                            "rate" : 400,
                            "rateCardId" : 1,
                            "month" : 201801
                    },
                    {                               // matched and updated
                            "productId" : 1234,
                            "rate" : 500,
                            "rateCardId" : 1,
                            "month" : 201802
                    },
                    {
                            "productId" : 1234,
                            "rate" : 400,
                            "rateCardId" : 2,
                            "month" : 201803
                    },
                    {
                            "productId" : 1235,
                            "rate" : 500,
                            "rateCardId" : 1,
                            "month" : 201801
                    },
                    {
                            "productId" : 1235,
                            "rate" : 234,
                            "rateCardId" : 2,
                            "month" : 201803
                    },
                    {                              // This was appended
                            "productId" : 1235,
                            "rate" : 700,
                            "rateCardId" : 1,
                            "month" : 201802
                    }
            ]
    }
    

    実際にbulkWrite()と一致しなかった要素の数によって異なります 応答は、それらのステートメントのどれだけが実際にドキュメントに一致し、影響を与えたかについて報告します。この場合は2 「最初の」更新操作は既存の配列エントリと一致し、「最後の」変更更新はドキュメントに配列エントリが含まれていないことと一致し、 $ push 変更します。

    結論

    つまり、組み合わせたアプローチがあります。

    • 質問の「更新」の最初の部分は非常に簡単で、単一のステートメントで実行できます。 、最初のセクションで示されているように。

    • 「現在存在しない」配列要素がある2番目の部分 現在のドキュメント配列内では、実際にはを使用する必要があります。 BulkWrite() 1つのリクエストで「複数の」操作を発行するため。

    したがって、更新 、は1回の操作に対して「YES」です。しかし、違いを追加する 複数の操作を意味します。ただし、ここに示すように、2つのアプローチを組み合わせることができます。

    コードを使用して「changeset」配列の内容に基づいてこれらのステートメントを作成する「凝った」方法はたくさんあるので、各メンバーを「ハードコーディング」する必要はありません。

    JavaScriptの基本的なケースとして、mongoシェルの現在のリリースと互換性があります(オブジェクト拡散演算子をサポートしていません):

    db.getCollection('avail_rates_copy').drop();
    db.getCollection('avail_rates_copy').insert(
      {
        "_id" : 12345,
        "_class" : "com.example.ProductRates",
        "rates" : [
          {
            "productId" : 1234,
            "rate" : 100,
            "rateCardId" : 1,
            "month" : 201801
          },
          {
            "productId" : 1234,
            "rate" : 200,
            "rateCardId" : 1,
            "month" : 201802
          },
          {
            "productId" : 1234,
            "rate" : 400,
            "rateCardId" : 2,
            "month" : 201803
          },
          {
            "productId" : 1235,
            "rate" : 500,
            "rateCardId" : 1,
            "month" : 201801
          },
          {
            "productId" : 1235,
            "rate" : 234,
            "rateCardId" : 2,
            "month" : 201803
          }
        ]
      }
    );
    
    var changeSet = [
      {
          "productId" : 1234, 
          "rate" : 400.0, 
          "rateCardId": 1,
          "month" : 201801
      }, 
      {
          "productId" : 1234, 
          "rate" : 500.0, 
          "rateCardId": 1,
          "month" : 201802
      }, 
      {
    
          "productId" : 1235, 
          "rate" : 700.0, 
          "rateCardId": 1,
          "month" : 201802
      }
    ];
    
    var arrayFilters = changeSet.map((obj,i) => 
      Object.keys(obj).filter(k => k != 'rate' )
        .reduce((o,k) => Object.assign(o, { [`u${i}.${k}`]: obj[k] }) ,{})
    );
    
    var $set = changeSet.reduce((o,r,i) =>
      Object.assign(o, { [`rates.$[u${i}].rate`]: r.rate }), {});
    
    var updates = [
      { "updateOne": {
        "filter": { "_id": 12345 },
        "update": { $set },
        arrayFilters
      }},
      ...changeSet.map(obj => (
        { "updateOne": {
          "filter": {
            "_id": 12345,
            "rates": {
              "$not": {
                "$elemMatch": Object.keys(obj).filter(k => k != 'rate')
                  .reduce((o,k) => Object.assign(o, { [k]: obj[k] }),{})
              }
            }
          },
          "update": {
            "$push": {
              "rates": obj
            }
          }
        }}
      ))
    ];
    
    db.getCollection('avail_rates_copy').bulkWrite(updates,{ ordered: true });
    

    これにより、次のような「一括」更新操作のリストが動的に作成されます。

    [
      {
        "updateOne": {
          "filter": {
            "_id": 12345
          },
          "update": {
            "$set": {
              "rates.$[u0].rate": 400,
              "rates.$[u1].rate": 500,
              "rates.$[u2].rate": 700
            }
          },
          "arrayFilters": [
            {
              "u0.productId": 1234,
              "u0.rateCardId": 1,
              "u0.month": 201801
            },
            {
              "u1.productId": 1234,
              "u1.rateCardId": 1,
              "u1.month": 201802
            },
            {
              "u2.productId": 1235,
              "u2.rateCardId": 1,
              "u2.month": 201802
            }
          ]
        }
      },
      {
        "updateOne": {
          "filter": {
            "_id": 12345,
            "rates": {
              "$not": {
                "$elemMatch": {
                  "productId": 1234,
                  "rateCardId": 1,
                  "month": 201801
                }
              }
            }
          },
          "update": {
            "$push": {
              "rates": {
                "productId": 1234,
                "rate": 400,
                "rateCardId": 1,
                "month": 201801
              }
            }
          }
        }
      },
      {
        "updateOne": {
          "filter": {
            "_id": 12345,
            "rates": {
              "$not": {
                "$elemMatch": {
                  "productId": 1234,
                  "rateCardId": 1,
                  "month": 201802
                }
              }
            }
          },
          "update": {
            "$push": {
              "rates": {
                "productId": 1234,
                "rate": 500,
                "rateCardId": 1,
                "month": 201802
              }
            }
          }
        }
      },
      {
        "updateOne": {
          "filter": {
            "_id": 12345,
            "rates": {
              "$not": {
                "$elemMatch": {
                  "productId": 1235,
                  "rateCardId": 1,
                  "month": 201802
                }
              }
            }
          },
          "update": {
            "$push": {
              "rates": {
                "productId": 1235,
                "rate": 700,
                "rateCardId": 1,
                "month": 201802
              }
            }
          }
        }
      }
    ]
    

    一般的な回答の「長い形式」で説明されているのと同じですが、もちろん、これらのステートメントをすべて作成するために、入力された「配列」コンテンツを使用するだけです。

    このような動的オブジェクト構築は任意の言語で実行でき、すべてのMongoDBドライバーは、「操作」が許可されているある種の構造の入力を受け入れ、実際にサーバーに送信されて実行される前にBSONに変換されます。




    1. Redisで特殊文字を含む数十万のキーを一括削​​除する方法

    2. バッチでmongoDBレコードを検索する(mongoid ruby​​アダプターを使用)

    3. mongodb正規表現で一致するマルチバイトutf8文字

    4. マングースにコレクション内のすべてのドキュメントを一覧表示させるにはどうすればよいですか?コレクションが空かどうかを確認するには?