pushOrModify like operator for mongo subdocument

前端 未结 1 1636
梦谈多话
梦谈多话 2021-01-26 08:15

What I\'m trying to do is that if subdoc is exist, update it and otherwise, push new subdoc by only one method.

First, find { name: \"SweetTown\" } and then

相关标签:
1条回答
  • 2021-01-26 08:42

    This actually requires "two" ( or "three" with upsert ) update statements and is one of the very good reasons why "Bulk" operations exist.

    db.collection.bulkWrite([
      // Attempt to update the matched element
      { "updateOne": {
        "filter": { 
          "name": "SweetTown",
          "residents.name": "Bob"
        },
        "update": {
          "$set": { "residents.$.reputation": 30 }    
        }
      },
      // $push the element where not matched
      { "updateOne": {
        "filter": {
          "name": "SweetTown",
          "residents.name": { "$ne": "Bob" } 
        },
        "update": { 
          "$push": { 
            "residents": { "name": "Bob", "reputation": 30 } 
          }
        } 
      }}
    ])
    

    Or if you actually wanted to include an "upsert" for the basic document of "SweetTown" then you need to separate out that concern into it's own test:

    db.collection.bulkWrite([
      // Attempt to update the matched element
      { "updateOne": {
        "filter": { 
          "name": "SweetTown",
          "residents.name": "Bob"
        },
        "update": {
          "$set": { "residents.$.reputation": 30 }    
        }
      },
      // $push the element where not matched
      { "updateOne": {
        "filter": {
          "name": "SweetTown",
          "residents.name": { "$ne": "Bob" } 
        },
        "update": { 
          "$push": { 
            "residents": { "name": "Bob", "reputation": 30 } 
          }
        } 
      }},
      // Only use $setOnInsert when actually an upsert
      { "updateOne": {
          "filter": {
            "name": "SweetTown"
          },
          "update": {
            "$setOnInsert": {
               "residents": [{ "name": "Bob", "reputation": 30 }]
            }
          },
          "upsert": true
      }}
    ])
    

    So the general concept there is to only apply the $setOnInsert action when an "upsert" actually occurs. In order to make sure this only happens in this case, the other operations actually looking at the array element are not marked with the "upsert" option. That part is on purpose.

    Whatever way you look at it, it is only ever possible for one of those operations to actually make any modification in the database, as either the element is found or not, or even the document is not found and a new one created.

    In no case is it possible to do that sort of operation in a single update statement. However since "Bulk" operations are really only one request with one response, then as far as your application is concerned then it only needed to talk to the server once to have the server try all of these three things and return a response.

    For earlier usage of the direct Bulk API, then the alternate syntax is:

    var bulk = db.collection.initializeOrderedBulkOp();
    
    // $set matched where existing
    bulk.find({ "name": "SweetTown", "residents.name": "Bob" }).updateOne({
      "$set": { "residents.$.reputation": 30 }
    });
    
    // $push where not existing
    bulk.find({ "name": "SweetTown", "residents.name": { "$ne": "Bob" } }).updateOne({
      "$push": { "residents": { "name": "Bob", "reputation": 30 } }
    });
    
    // Attempt to upsert only
    bulk.find({ "name": "SweetTown" }).upsert().updateOne({
      "$setOnInsert": {
        "residents": [{ "name": "Bob", "reputation": 30 }]
      }
    })
    
    bulk.execute();
    
    0 讨论(0)
提交回复
热议问题