web-dev-qa-db-ja.com

MongoDB:サブドキュメントのアップサート

bars.nameに一意のインデックスを持つ、そのようなもののドキュメントがあります。

{ name: 'foo', bars: [ { name: 'qux', somefield: 1 } ] }

{ name: 'foo', 'bars.name': 'qux' }および$set: { 'bars.$.somefield': 2 }のサブドキュメントを更新するか、{ name: 'qux', somefield: 2 }の下に{ name: 'foo' }を含む新しいサブドキュメントを作成します。

アップサート付きの単一のクエリを使用してこれを行うことは可能ですか、それとも2つの個別のクエリを発行する必要がありますか?

関連: 埋め込みドキュメントの「upsert」 (サブドキュメント識別子をキーとしてスキーマを変更することをお勧めしますが、これは2年前のものであり、より良い解決策があるかどうか疑問に思っています今。)

32
shesek

いいえ、これに対する本当に良い解決策はありません。

表示されているような構造を持つドキュメントがあるとします。

{ 
  "name": "foo", 
  "bars": [{ 
       "name": "qux", 
       "somefield": 1 
  }] 
}

このような更新を行う場合

db.foo.update(
    { "name": "foo", "bars.name": "qux" },
    { "$set": { "bars.$.somefield": 2 } },
    { "upsert": true }
)

一致するドキュメントが見つかったため、すべてが正常です。しかし、「bars.name」の値を変更した場合:

db.foo.update(
    { "name": "foo", "bars.name": "xyz" },
    { "$set": { "bars.$.somefield": 2 } },
    { "upsert": true }
)

その後、障害が発生します。ここで実際に変更された唯一のことは、MongoDB 2.6以降ではエラーがもう少し簡潔になったことです。

WriteResult({
    "nMatched" : 0,
    "nUpserted" : 0,
    "nModified" : 0,
    "writeError" : {
        "code" : 16836,
        "errmsg" : "The positional operator did not find the match needed from the query. Unexpanded update: bars.$.somefield"
    }
})

それはいくつかの点で優れていますが、とにかく「アップサート」したくないのです。あなたがしたいことは、「名前」が現在存在しない配列に要素を追加することです。

したがって、本当に必要なのは、「upsert」フラグなしの更新試行の「結果」であり、影響を受けたドキュメントがあるかどうかを確認します。

db.foo.update(
    { "name": "foo", "bars.name": "xyz" },
    { "$set": { "bars.$.somefield": 2 } }
)

応答の降伏:

WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })

したがって、変更されたドキュメントが0である場合、次の更新を発行する必要があることがわかります。

db.foo.update(
    { "name": "foo" },
    { "$Push": { "bars": {
        "name": "xyz",
        "somefield": 2
    }}
)

本当にあなたが望むことを正確に行う他の方法はありません。配列への追加は厳密には「セット」タイプの操作ではないため、$addToSet"bulk update" 機能と組み合わせて使用​​することはできません。更新リクエスト。

この場合、結果を確認するか、ドキュメント全体を読み、コードで新しい配列要素を更新または挿入するかどうかを確認する必要があるようです。

43
Neil Lunn

スキーマを少し変更し、次のような構造にしたい場合:

{ "name": "foo", "bars": { "qux": { "somefield": 1 },
                           "xyz": { "somefield": 2 },
                  }
}

一度に操作を実行できます。繰り返し 埋め込み文書の「upsert」 完全を期すため

3
nesdis

更新が更新中のレコードへの参照に依存している場合(たとえば、更新x => x + 1)、これを行う方法はありません。 2つの別個のコマンド(設定してから挿入)を発行すると、重複を確認しても解決できない競合状態が発生します。重複のために挿入が拒否されると、更新の効果が失われます(たとえば、x上記の例で適切にインクリメントされます)。 MongoDBがこの機能を追加した場合、特定のアプリケーションでは埋め込みドキュメントを実装する機能がMongoDBの大きな魅力となるため、素晴らしいでしょう。

1
bluenike

2つのクエリでそれを行う方法がありますが、bulkWriteでも機能します。

私の場合、バッチ処理ができないのが最大の問題だからです。このソリューションを使用すると、最初のクエリの結果を収集する必要がなく、必要に応じて一括操作を実行できます。

この例で実行する2つの連続したクエリを次に示します。

// Update subdocument if existing
collection.updateMany({
    name: 'foo', 'bars.name': 'qux' 
}, {
    $set: { 
        'bars.$.somefield': 2 
    }
})
// Insert subdocument otherwise
collection.updateMany({
    name: 'foo', $not: {'bars.name': 'qux' }
}, {
    $Push: { 
        bars: {
            somefield: 2, name: 'qux'
        }
    }
})

これには、複数のアプリケーションがデータベースに同時に書き込みを行っている場合に、破損したデータ/競合状態がないという利点もあります。 2つのアプリケーションが同時に同じクエリを実行する場合、ドキュメント内の2つのbars: {somefield: 2, name: 'qux'}サブドキュメントで終わるリスクはありません。

0
coyotte508