mongoose update array or add to the array

后端 未结 2 1448
刺人心
刺人心 2021-01-27 09:28

I\'ve been trying to get this running for a while now but I can\'t figure out what I\'m doing wrong.

I have two schemas like this

const paymentSchema = n         


        
相关标签:
2条回答
  • 2021-01-27 10:22

    Consider the two input documents. The first one will be inserted (the year_month: '2020_03' doesn't exist in the payments array). When the update is run with the second one it will update the statusof the existing sub-document in the array.

    The update operation is valid with the MongoDB version 4.2 or later only, as it uses a pipeline for the update.

    INPUT_DOC = { year_month: '2020_03', status: false }    // this will get inserted into the array
    INPUT_DOC = { year_month: '2020_02', status: true }     // updates the sub-document
    
    db.collection.findOneAndUpdate(
      { 
          _id: "5e90ae0e0ed9974174e92826" 
      },
      [ 
          { 
              $set: { 
                  payments: {
                      $reduce: {
                          input: "$payments", 
                          initialValue: { payments: [], update: false },
                          in: {
                              $cond: [ { $eq: [ "$$this.year_month", INPUT_DOC.year_month ] },
                                       { 
                                          payments: { 
                                              $concatArrays: [
                                                   [ { _id: "$$this._id", year_month: "$$this.year_month", status: INPUT_DOC.status } ],
                                                   "$$value.payments"
                                               ] 
                                          }, 
                                          update: true
                                       },
                                       { 
                                          payments: { 
                                              $concatArrays: [  [ "$$this" ], "$$value.payments" ] 
                                          }, 
                                          update: "$$value.update" 
                                       }
                              ]
                          }
                      }
                  }
              }
          },
          { 
              $set: { 
                  payments: { 
                      $cond: [ { $eq: [ "$payments.update", false ] },
                               { $concatArrays: [ [ INPUT_DOC ], "$payments.payments" ] },
                               { $concatArrays: [ [ ], "$payments.payments" ] }
                      ] 
                  }
              }
          }
      ],
      { 
          new: true, 
          upsert: true 
      }
    )
    
    0 讨论(0)
  • 2021-01-27 10:30

    The main problem is that findOneAndUpdate does exactly what its name implies. It executes a find using the provided filter, and if a match is found, applies the updates to the first matching document.

    If the collection contains only this document:

    [
        {
            "_id": "5e90ae0e0ed9974174e92826",
            "payments": [
                {
                    "year_month": "2020_02",
                    "status": false
                }
            ]
        }
    ]
    

    The initial find part is essentially

    .find({
            _id: '5e90ae0e0ed9974174e92826',
            payments: { $elemMatch: { year_month: '2020_03' }}
    })
    

    This matches nothing, and since upsert is set to true, fineOneAndUpdate attempts to create a brand new document. Even if it were able to create an array from an unmatched positional operator, the document it would be trying to add would be:

     {
            "_id": "5e90ae0e0ed9974174e92826",
            "payments": [
                {
                    "year_month": "2020_03",
                    "status": false
                }
            ]
    }
    

    This is not correct, and would fail to insert due to duplicate _id value anyway.

    If you are using MongoDB 4.2, you could use an aggregation pipeline as the second argument to findAndUpdate to check the array for the element you are interested in and add it if it is missing.

    One not very pretty method is below. The findOneAndUpdate will match the _id, and the pipeline will:
    - check if any element in the array matches the desired year_month
    - If so, $reduce the array to update the status field in that element
    - If not, append a new element
    - Assign the result back to payments

    .findOneAndUpdate(
        { "_id": "5e90ae0e0ed9974174e92826" },
        [{$set: {
             payments: {$cond:[
                     {$gt:[
                           {$size:
                                 {$filter:{
                                      input:"$payments", 
                                      cond:{$eq:["$$this.year_month","2020_03"]}
                           }}},
                           1
                      ]},
                      {$reduce:{
                            input:"$payments",
                            initialValue:[],
                            in:{$concatArrays:[
                                      "$$value",
                                      [{$cond:[
                                           {$eq:["$$this.j",3]},
                                           {$mergeObjects:["$$this",{status:true}]},
                                           "$$this"
                                      ]}]
                            ]}
                      }},
                      {$concatArrays:[
                           "$payments",
                           [{year_month:"2020_03", status:true}]
                      ]}
              ]}
         }}]
    )
    
    0 讨论(0)
提交回复
热议问题