mongodbで複数の配列要素を更新する方法


183

要素の配列を保持するMongoドキュメントがあります。

= XXの.handled配列内のすべてのオブジェクトの属性をリセットしたいと思います.profile

ドキュメントは次の形式です。

{
    "_id": ObjectId("4d2d8deff4e6c1d71fc29a07"),
    "user_id": "714638ba-2e08-2168-2b99-00002f3d43c0",
    "events": [{
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 20,
            "data": "....."
        }
        ...
    ]
}

だから、私は以下を試しました:

.update({"events.profile":10},{$set:{"events.$.handled":0}},false,true)

ただし、各ドキュメントで最初に一致した配列要素のみを更新します。(これは$-位置演算子の定義された動作です。)

一致した配列要素をすべて更新するにはどうすればよいですか?


2
サブセットまたはすべてのアレイの項目を更新する3.6 MongoDBのために追加されました:docs.mongodb.com/manual/reference/operator/update/...
ヤープ

必ずarrayFiltersをチェックアウトし、更新を効率的にするために使用するクエリを検討してください。Neil Lunnによる回答をご覧ください:stackoverflow.com/a/46054172/337401
Jaap

私の答えを確認してください
ウクデミール

回答:


111

現時点では、位置演算子を使用して配列内のすべてのアイテムを更新することはできません。JIRA http://jira.mongodb.org/browse/SERVER-1243を参照してください

回避策として、次のことができます。

  • 各アイテムを個別に更新する(events.0.handled events.1.handled ...)または...
  • ドキュメントを読んで、手動で編集を行い、それを古いものと置き換えて保存します(アトミック更新を確実にしたい場合は、[現在の場合は更新]をチェックします)

15
同様の問題がある場合は、この問題に投票してください-jira.mongodb.org/browse/SERVER-1243
LiorH

私は実際にはドキュメントの読み取りと保存のアプローチが好きです。しかし、モンゴの前にCouchを使用したので、CouchのクエリAPIはなく、ドキュメント全体のREST APIがあるため、アプローチはより自然に見えます
adam

1
これらのアプローチはどちらもかなり大量のメモリを必要としますよね?検索する必要のあるドキュメントがたくさんあり、更新するためにそれらすべて(またはネストされた配列)をロードする必要がある場合... +これを非同期で実行する必要がある場合は、実装が少し面倒です...
Ixx

13
すべての技術的な問題はさておき、この機能がMongoDBで利用できないのは驚くべきことです。この制約により、データベーススキーマをカスタマイズする必要がなくなります。
Sung Cho、

5
Neil Lunn stackoverflow.com/a/46054172/337401がバージョン3.6でこれに回答しました。これは人気のある質問なので、この受け入れられた回答をニール・ランによる回答への紹介で更新することは価値があるかもしれません。
Jaap

73

MongoDB 3.6リリース(およびMongoDB 3.5.12の開発ブランチで利用可能)により、単一のリクエストで複数の配列要素を更新できるようになりました。

これは、このバージョンで導入されたフィルターされた位置$[<identifier>]更新演算子構文を使用します。

db.collection.update(
  { "events.profile":10 },
  { "$set": { "events.$[elem].handled": 0 } },
  { "arrayFilters": [{ "elem.profile": 10 }], "multi": true }
)

"arrayFilters"するためのオプションに渡されたとして.update()、あるいは .updateOne().updateMany().findOneAndUpdate()または.bulkWrite()方法は、更新文で与えられた識別子に一致する条件を指定します。指定された条件に一致するすべての要素が更新されます。

そのことに注意してください "multi"これは「複数の要素を更新する」という期待に質問のコンテキストで指定されたが使用された。ここでの使用は、常にそうであったように、または最新のAPIバージョンのの必須設定として現在指定されているように、「複数のドキュメント」に適用され.updateMany()ます。

やや皮肉なことに、これはメソッドの「オプション」引数などで指定されるため.update()、構文は通常、最近のすべてのリリースドライババージョンと互換性があります。

ただし、これはmongoシェルには当てはまりません。メソッドがそこで実装される方法(「皮肉なことに後方互換性」)のため、arrayFilters引数は以前のバージョンとの「後方互換性」を実現するためにオプションを解析する内部メソッドによって認識および削除されませんMongoDBサーバーのバージョンと「レガシー」.update()API呼び出し構文。

したがって、mongoシェルまたは他の「シェルベース」の製品(特にRobo 3T)でコマンドを使用する場合は、3.6以降の開発ブランチまたは製品リリースの最新バージョンが必要です。

こちらもご覧ください positional all $[]また、「複数の配列要素を」更新しますが、指定された条件に適用せずとに適用されるすべてのことは、所望の作用である配列の要素。

これらの新しい位置演算子が「配列が他の配列内にある」「ネストされた」配列構造にどのように適用されるかについては、MongoDBを使用したネストされた配列の更新も参照してください。

重要 -以前のバージョンからのアップグレードされたインストールでは、MongoDB機能が有効になっていない可能性があるため、ステートメントが失敗する可能性もあります。アップグレード手順がインデックスのアップグレードなどの詳細で完了していることを確認してから、実行する必要があります

   db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )

または、インストールされているバージョンに適用可能なより高いバージョン。つまり"4.0"、現在のバージョン4以降。これにより、新しい位置更新演算子などの機能が有効になりました。次の方法でも確認できます。

   db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

現在の設定に戻すには


9
承認された回答を更新し、この回答を参照してください。
Jaap

2
なにelem
user1063287 2018

1
これは正しいです。RoboMongoはまだサポートしarrayFiltersていないため、CLI経由でアップデートを実行することに注意してください。stackoverflow.com/questions/48322834/...
drlff

ニール、特に重要なセクションに感謝します。まさに私が必要とするものでした
janfabian

このコードはpymongoでERRORを返します。エラーです:TypeErrorを発生させます( "%sはTrueまたはFalseでなければなりません"%(オプション))TypeError:upsertはTrueまたはFalseでなければなりません
Vagif

67

私のために働いたのはこれでした:

db.collection.find({ _id: ObjectId('4d2d8deff4e6c1d71fc29a07') })
  .forEach(function (doc) {
    doc.events.forEach(function (event) {
      if (event.profile === 10) {
        event.handled=0;
      }
    });
    db.collection.save(doc);
  });

mongoの初心者や、JQueryや友達に慣れている人にとっては、それはより明確だと思います。


私は使用db.posts.find({ 'permalink':permalink }).forEach( function(doc) {...していますが、取得していますOops.. TypeError: Object # has no method 'forEach'
Squirrl 2013

3
@Squirrlは古いmongodbバージョンである可能性がありますか?ドキュメントでは、カーソルにforEach関数を適用する方法は明確ですが、サポートされているバージョンが記載されていないためです。docs.mongodb.org/manual/reference/method/cursor.forEach
Daniel Cerecedo

@Squirrl trydb.posts.find(...).toArray().forEach(...)
marmor

使用せずにこれを行うことはできませんJavascriptか JavaScript APIを使用せずに、mongoシェルから直接この更新を実行したい。
Meliodas 2017年

1
このクエリをJava用のmongodbドライバーで記述したり、spring-data-mongodbで記述したりできますか?ありがとう、クリス
チク

18

これは、更新されていないサブドキュメントがまだ残っているドキュメントが残っているかどうかを確認するwhileループでも実行できます。この方法は、更新のアトミック性を保持します(ここでの他の多くのソリューションにはありません)。

var query = {
    events: {
        $elemMatch: {
            profile: 10,
            handled: { $ne: 0 }
        }
    }
};

while (db.yourCollection.find(query).count() > 0) {
    db.yourCollection.update(
        query,
        { $set: { "events.$.handled": 0 } },
        { multi: true }
    );
}

ループが実行される回数は、コレクション内の任意のドキュメントで、profile10にhandled等しく0以外のサブドキュメントが発生する最大回数に等しくなります。したがって、コレクションに100のドキュメントがあり、そのうちの1つに一致する3つのサブドキュメントがある場合queryあり、他のすべてのドキュメントに一致するサブドキュメントが少ない場合、ループは3回実行されます。

このメソッドは、このスクリプトの実行中に別のプロセスによって更新される可能性のある他のデータを破壊する危険を回避します。また、クライアントとサーバー間で転送されるデータの量を最小限に抑えます。


13

これは実際にはhttp://jira.mongodb.org/browse/SERVER-1243での長期にわたる問題に関連しています。実際には、複数の配列の一致がサポートされる「すべてのケース」をサポートする明確な構文に対する多くの課題があります。見つかりました。実際には、一括操作など、この問題の解決策を「支援」する方法がすでに導入されています。この元の投稿の後に実装さされています。

単一のupdateステートメントで一致する配列要素を1つ以上更新することはまだ不可能です。そのため、「マルチ」更新を使用しても、更新できるのは、その単一のドキュメントごとに配列内の1つの計算された要素だけです。ステートメント。

現時点で可能な最良の解決策は、一致するすべてのドキュメントを見つけてループし、一括更新を処理することです。これにより、単一のリクエストで単一のリクエストで多くの操作を送信できるようになります。オプションでを使用.aggregate()して、検索結果で返される配列コンテンツを更新選択の条件に一致するものだけに減らすことができます。

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$project": {
        "events": {
            "$setDifference": [
               { "$map": {
                   "input": "$events",
                   "as": "event",
                   "in": {
                       "$cond": [
                           { "$eq": [ "$$event.handled", 1 ] },
                           "$$el",
                           false
                       ]
                   }
               }},
               [false]
            ]
        }
    }}
]).forEach(function(doc) {
    doc.events.forEach(function(event) {
        bulk.find({ "_id": doc._id, "events.handled": 1  }).updateOne({
            "$set": { "events.$.handled": 0 }
        });
        count++;

        if ( count % 1000 == 0 ) {
            bulk.execute();
            bulk = db.collection.initializeOrderedBulkOp();
        }
    });
});

if ( count % 1000 != 0 )
    bulk.execute();

.aggregate()アレイの「ユニーク」識別子または各要素に対するすべてのコンテンツがある場合が動作する部分は、「ユニーク」要素自体を形成します。これは、配列の一致を処理するために使用される操作から返された値$setDifferenceをフィルタリングするために使用される「セット」演算子が原因です。false$map

配列のコンテンツに一意の要素がない場合は、次の方法で別の方法を試すことができます$redact

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$redact": {
        "$cond": {
            "if": {
                "$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
            },
            "then": "$$DESCEND",
            "else": "$$PRUNE"
        }
    }}
])

制限があるのは、「処理された」が実際に他のドキュメントレベルに存在することを意図したフィールドであった場合、予期しない結果が得られる可能性が高いですが、そのフィールドが1つのドキュメント位置にのみ表示され、等価一致である場合は問題ありません。

執筆時点での将来のリリース(3.1 MongoDB以降)では、$filter操作がより簡単になります。

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$project": {
        "events": {
            "$filter": {
                "input": "$events",
                "as": "event",
                "cond": { "$eq": [ "$$event.handled", 1 ] }
            }
        }
    }}
])

また、サポート.aggregate()するすべてのリリースでは、で次のアプローチを使用できます$unwindが、その演算子を使用すると、パイプラインでの配列の拡張により、最も効率の悪いアプローチになります。

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$unwind": "$events" },
    { "$match": { "events.handled": 1 } },
    { "$group": {
        "_id": "$_id",
        "events": { "$push": "$events" }
    }}        
])

MongoDBバージョンが集約出力からの「カーソル」をサポートしているすべての場合、これは、アプローチを選択し、表示された同じコードブロックで結果を反復して、一括更新ステートメントを処理するだけの問題です。一括出力と集約出力からの「カーソル」は同じバージョン(MongoDB 2.6)で導入されているため、通常は連携して処理を行います。

以前のバージョンでも.find()、カーソルを返すためにだけ使用し、.update()反復のために配列要素が一致した回数だけステートメントの実行を除外するのがおそらく最善です。

db.collection.find({ "events.handled": 1 }).forEach(function(doc){ 
    doc.events.filter(function(event){ return event.handled == 1 }).forEach(function(event){
        db.collection.update({ "_id": doc._id },{ "$set": { "events.$.handled": 0 }});
    });
});

「マルチ」更新を行うことが絶対的に決まっている場合、または一致する各ドキュメントの複数の更新を処理するよりも最終的に効率的であると判断した場合は、可能な最大配列一致数を常に決定し、その数の「マルチ」更新を実行する基本的に、更新するドキュメントがなくなるまで。

MongoDB 2.4および2.2バージョンの有効なアプローチも.aggregate()、この値を見つけるために使用できます。

var result = db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$unwind": "$events" },
    { "$match": { "events.handled": 1 } },
    { "$group": {
        "_id": "$_id",
        "count": { "$sum": 1 }
    }},
    { "$group": {
        "_id": null,
        "count": { "$max": "$count" }
    }}
]);

var max = result.result[0].count;

while ( max-- ) {
    db.collection.update({ "events.handled": 1},{ "$set": { "events.$.handled": 0 }},{ "multi": true })
}

いずれにせよ、アップデート内で実行したくないことがいくつかあります。

  1. 「ワンショット」で配列を更新しないでください。コード内の配列コンテンツ全体を更新してから$set、各ドキュメントの配列全体を更新する方が効率的であると思われる場合。これは処理が高速に見えるかもしれませんが、配列の内容が読み取られてから更新された後、配列の内容が変更されていないという保証はありません。けれどもは$setそれだけで、それが正しいデータである「考えて」、したがって、読み取りと書き込みの間に発生するすべての変更を上書きする可能性があるものと配列を更新します、まだアトミック演算子です。

  2. 更新するインデックス値を計算しないでください。「ワンショット」アプローチと同様に、その位置0と位置を計算するだけで2(など)、更新する要素があり、次のような最終的なステートメントでこれらをコーディングします。

    { "$set": {
        "events.0.handled": 0,
        "events.2.handled": 0
    }}

    ここでも問題は、ドキュメントが読み取られたときに見つかったインデックス値が、更新時に配列内の同じインデックス値であるという「推定」です。順序を変更する方法で配列に新しい項目が追加されると、それらの位置は無効になり、実際には間違った項目が更新されます。

したがって、一致する複数の配列要素を単一の更新ステートメントで処理できるようにするために適切な構文が決定されるまで、基本的なアプローチは、一致する各配列要素を個別のステートメント(理想的にはBulk)で更新するか、基本的に最大の配列要素を計算することです変更された結果が返されなくなるまで、更新または更新を継続します。とにかく、ステートメントごとに1つの要素しか更新していない場合でも、一致した配列要素の位置の$更新を「常に」処理する必要があります。

一括操作は、実際には「複数の操作」であることが判明した操作を処理するための「一般化された」ソリューションであり、同じ値で複数の配列要素を単に更新するだけではなく、これより多くのアプリケーションがあるため、もちろん実装されています。すでに、そしてそれは現在この問題を解決するための最良のアプローチです。


8

これはまだモンゴで扱われていないことに驚いています。全体的なモンゴは、サブ配列を処理するときに優れているようには見えません。たとえば、単純にサブ配列を数えることはできません。

ハビエルの最初のソリューションを使用しました。配列をイベントに読み込み、ループしてセットexpを構築します。

var set = {}, i, l;
for(i=0,l=events.length;i<l;i++) {
  if(events[i].profile == 10) {
    set['events.' + i + '.handled'] = 0;
  }
}

.update(objId, {$set:set});

これは、条件テストのコールバックを使用して関数に抽象化できます


これをありがとう!この機能がまだネイティブでサポートされていないなんて信じられません!これを使用して、サブアレイのすべてのアイテムをインクリメントし、他のユーザーが読んでいる...すべてのアイテムを更新するには、ifステートメントを削除するだけです。
Zaheer 2014年

9
これは安全な解決策ではありません。更新の実行中にレコードが追加されると、データが破損します。
Merc

4

私はC#3.6の最新のドライバーを使用してこれに対する解決策を探していましたが、最終的に解決した修正を以下に示します。ここでのキーは、MongoDBによるとバージョン3.6で新しく追加された「$ []」を使用することです。https://docs.mongodb.com/manual/reference/operator/update/positional-all/#upを参照してください。S []詳細については。

これがコードです:

{
   var filter = Builders<Scene>.Filter.Where(i => i.ID != null);
   var update = Builders<Scene>.Update.Unset("area.$[].discoveredBy");
   var result = collection.UpdateMany(filter, update, new UpdateOptions { IsUpsert = true});
}

詳細については、元の投稿をここで参照してください。MongoDBC#ドライバーを使用してすべてのドキュメントから配列要素を削除します。


4

スレッドは非常に古いですが、私はここで答えを探しに来たので、新しいソリューションを提供します。

MongoDBバージョン3.6以降では、位置演算子を使用して配列内のすべてのアイテムを更新できるようになりました。こちらの公式ドキュメントをご覧ください

次のクエリは、ここで尋ねられた質問に対して機能します。また、Java-MongoDBドライバーで確認しましたが、正常に動作します。

.update(   // or updateMany directly, removing the flag for 'multi'
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},  // notice the empty brackets after '$' opearor
   false,
   true
)

これが私のような人に役立つことを願っています。


1

私は以下を試し、うまくいきました。

.update({'events.profile': 10}, { '$set': {'events.$.handled': 0 }},{ safe: true, multi:true }, callback function);

// nodejsの場合のコールバック関数


このコードは、配列内で最初に一致したアイテムを更新するだけです。間違った答え。
ハムレット

v2.6で動作します。古いバージョンである可能性がありますか?ここではそのドキュメントのだdocs.mongodb.com/manual/reference/method/db.collection.update/...は
Jialinゾウ

1

MongoDBのすべての要素を更新できます

db.collectioname.updateOne(
{ "key": /vikas/i },
{ $set: { 
 "arr.$[].status" : "completed"
} }
)

「arr」配列のすべての「ステータス」値を「完了」に更新します

1つのドキュメントのみの場合

db.collectioname.updateOne(
 { key:"someunique", "arr.key": "myuniq" },
 { $set: { 
   "arr.$.status" : "completed", 
   "arr.$.msgs":  {
                "result" : ""
        }
   
 } }
)

ただし、1つではなく、配列内のすべてのドキュメントを更新したくない場合は、要素とifブロック内をループする必要があります。

db.collectioname.find({findCriteria })
  .forEach(function (doc) {
    doc.arr.forEach(function (singlearr) {
      if (singlearr check) {
        singlearr.handled =0
      }
    });
    db.collection.save(doc);
  });

0

実際、saveコマンドはDocumentクラスのインスタンスのみにあります。メソッドと属性がたくさんあります。したがって、lean()関数を使用して作業負荷を軽減できます。こちらをご参照ください。https://hashnode.com/post/why-are-mongoose-mongodb-odm-lean-queries-faster-than-normal-queries-cillvawhq0062kj53asxoyn7j

保存機能のもう1つの問題は、マルチセーブで同時にデータが競合することです。 Model.Updateはデータを一貫して作成します。したがって、ドキュメントの配列内のマルチアイテムを更新します。使い慣れたプログラミング言語を使用して、次のようなことを試してください。私はマングースを使用しています。

User.findOne({'_id': '4d2d8deff4e6c1d71fc29a07'}).lean().exec()
  .then(usr =>{
    if(!usr)  return
    usr.events.forEach( e => {
      if(e && e.profile==10 ) e.handled = 0
    })
    User.findOneAndUpdate(
      {'_id': '4d2d8deff4e6c1d71fc29a07'},
      {$set: {events: usr.events}},
      {new: true}
    ).lean().exec().then(updatedUsr => console.log(updatedUsr))
})

0

$ []演算子はすべてのネストされた配列を選択します。すべての配列項目は '$ []'で更新できます

.update({"events.profile":10},{$set:{"events.$[].handled":0}},false,true)

参照


ここで最後に「false、true」を含める理由を説明していただけますか?ドキュメントにそれが見つかりません。
ガーソン

間違った答えall位置演算子$[] は、指定された配列のすべてのフィールドを更新するだけです。機能するのは$[identifier]、指定された条件に一致する配列フィールドを操作するフィルターされた位置演算子です。して使用する必要がありますarrayFilters :Refrence docs.mongodb.com/manual/release-notes/3.6/#arrayfiltersdocs.mongodb.com/manual/reference/operator/update/...
ローレンス・イーグルス

0

このスレッドで$ []の使用を提案するいくつかの回答は間違っていることに注意してください。

db.collection.update(
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},
   {multi:true}
)

上記のコードは、「プロファイル」の値に関係なく、「イベント」配列のすべての要素の「ハンドル」を0に更新します。クエリ{"events.profile":10}は、配列全体のドキュメントではなく、ドキュメント全体をフィルターするだけです。この状況では、配列項目の条件を指定するために$[elem]with を使用する必要があるarrayFiltersため、Neil Lunnの答えは正しいです。


0

mongo dbの複数のドキュメントの配列フィールドを更新します。

$ pullまたは$ pushをupdate manyクエリで使用して、mongoDbの配列要素を更新します。

Notification.updateMany(
    { "_id": { $in: req.body.notificationIds } },
    {
        $pull: { "receiversId": req.body.userId }
    }, function (err) {
        if (err) {
            res.status(500).json({ "msg": err });
        } else {
            res.status(200).json({
                "msg": "Notification Deleted Successfully."
            });
        }
    });

0

最初$に、配列内の更新する要素を識別するだけで、配列内での位置を明示的に指定しない位置演算子を使用していたため、コードは機能しませんでした。

必要なのは、フィルターされた位置演算子$[<identifier>]です。配列フィルター条件に一致するすべての要素を更新します。

解決:

db.collection.update({"events.profile":10}, { $set: { "events.$[elem].handled" : 0 } },
   {
     multi: true,
     arrayFilters: [ { "elem.profile": 10 } ]
})

ここmongodb docにアクセス

コードの機能:

  1. {"events.profile":10} コレクションをフィルタリングし、フィルターに一致するドキュメントを返します

  2. $set更新演算子:それが動作する文書のフィールドに一致する修正。

  3. {multi:true}.update()フィルターに一致するすべてのドキュメントを変更するため、次のように動作しますupdateMany()

  4. { "events.$[elem].handled" : 0 } and arrayFilters: [ { "elem.profile": 10 } ] この手法では、arrayFiltersでフィルターされた位置配列を使用します。ここでフィルターされた定位置配列$[elem]は、配列フィルターで指定された条件に一致する配列フィールド内のすべての要素のプレースホルダーとして機能します。

配列フィルター

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.