簡體   English   中英

當且僅當新文檔中存在元素數據時更新數組元素 mongodb

[英]Update element of an array if and only if data for element is present in new document mongodb

我在更新數組時遇到問題。

樣本文件:

_id:11,
marksObtained:[{physics:10}, {chemistry:12}, {maths: 11}, {biology:9}, {social:9}]
name:"Someone"

要更新的字段是:

[{chemistry:13},{zoology:12}]

所以新文件看起來像:

_id:11,
marksObtained:[{physics:10}, {chemistry:13}, {maths: 11}, {biology:9}, {social:9},{zoology:12}]
name:"Someone"

化學標記更新為 13,rest 值保持原樣

我試過 $push, $addToSet, update, updateOne,updateMany

我想避免編寫迭代和比較標簽的代碼。

鍵和值來自動態設備,所以我想更新數組中已經存在的鍵,如果有新鍵,它必須附加到數組中

這其實是一種痛苦。 但是有幾個選擇:

1 - 批量操作

您的操作只是一個$pull (從數組中刪除現有值,如果存在)和$push (添加正確的值)。 您可以通過批量操作 api 來確保寫入盡可能原子化:

const bulkOperation = db.students.initializeOrderedBulkOp()
bulkOperation.find({_id:11}).updateOne({$pull: {marksObtained: { chemistry: { $exists: true } } } })
bulkOperation.find({_id:11}).updateOne({$push: {marksObtained: { chemistry: 12 } } })
bulkOperation.execute()

例如:動態地在代碼中:

const _idToUpdate = 11
const updateFields = [{ chemistry: 13 }, { zoology: 12 }]

const bulkOperation = db.collection('students').initializeOrderedBulkOp()
for (const field of updateFields) {
  for (const key in field) {
    const value = field[key]

    const pullOp = { $pull: { marksObtained: { } } }
    pullOp.$pull.marksObtained[key] = { $exists: true }

    const pushOp = { $push: { marksObtained: { } } }
    pushOp.$push.marksObtained[key] = value

    console.log('name', key, 'value', value, 'pullOp', JSON.stringify(pullOp), 'pushOp', JSON.stringify(pushOp))

    bulkOperation.find({ _id: _idToUpdate }).updateOne(pullOp)
    bulkOperation.find({ _id: _idToUpdate }).updateOne(pushOp)
  }
}
bulkOperation.execute()

2 - 自定義$function運行 javascript 來設置值

根據這個游樂場: https://mongoplayground.net/p/PcV6dMoyJ6Y

您可以設置自定義 javascript function 來為您完成臟活。

令人討厭的是,在操場上,它的格式不是很好,因此它在一行中,但是一個例子是這樣的:

db.collection.update({
  _id: 11
},
[
  {
    $set: {
      marksObtained: {
        $function: {
          body: "function(marks) { const updateFields = { chemistry: 13, zoology: 12, french: 11 }; for (const updateFieldName in updateFields) { let updated = false;for (const markObtained of marks) { for (const markObtainedKey in markObtained) { if (markObtainedKey === updateFieldName) { markObtained[markObtainedKey] = updateFields[updateFieldName]; updated = true; } } } if (!updated) { const newTopic = {}; newTopic[updateFieldName] = updateFields[updateFieldName]; marks.push(newTopic); } } return marks }",
          args: [
            "$marksObtained"
          ],
          lang: "js"
        }
      }
    }
  }
])

其中 function 是:

const updateFields = { chemistry: 13, zoology: 12, french: 11 }
for (const updateFieldName in updateFields) {
  let updated = false
  for (const markObtained of marks) {
    for (const markObtainedKey in markObtained) {
      if (markObtainedKey === updateFieldName) {
        markObtained[markObtainedKey] = updateFields[updateFieldName]
        updated = true
      }
    }
  }
  if (!updated) {
    const newTopic = {}
    newTopic[updateFieldName] = updateFields[updateFieldName]
    marks.push(newTopic)
  }
}
return marks

顯然,您必須像我所做的那樣按照單個 object 設置值,但您可以根據需要進行調整。


注意:如果您將marksObtained格式化為:

marksObtained: {
  physics: 10,
  chemistry: 12,
  maths: 11,
  biology: 9,
  social: 9
}

要么

marksObtained: [
  {lesson: 'physics', score: 10},
  {lesson: 'chemistry', score: 12},
  {lesson: 'maths', score: 11},
  {lesson: 'biology', score: 9},
  {lesson: 'social', score: 9}
]

擁有一個包含單個 object 的數組並沒有多大意義。

它可以在一個更新查詢中完成。 如果數組中項目的順序很重要,那就有點笨拙了。 否則它可以很優雅:

項目的順序很重要

db.collection.updateMany(
  {_id: 11},
  [
    {$set: {
      marksObtained: {
        $map: {
          input: "$marksObtained",
          in: {$first: {$objectToArray: "$$this"}}
        }
      },
      dataToUpdate: {
        $map: {
          input: [
            {chemistry: 13},
            {zoology: 12}
          ],
          in: {$first: {$objectToArray: "$$this"}}
        }
      }
  }},
  {$set: {
      updateData: {
        $filter: {
          input: "$dataToUpdate",
          cond: {$in: ["$$this.k", "$marksObtained.k"]}
        }
      },
      newData: {
        $filter: {
          input: "$dataToUpdate",
          cond: {$not: {$in: ["$$this.k", "$marksObtained.k"]}}
        }
      },
      dataToUpdate: "$$REMOVE"
  }},
  {$set: {
      marksObtained: {
        $concatArrays: [
          {
            $map: {
              input: "$marksObtained",
              in: {
                $cond: [
                  {$in: ["$$this.k", "$updateData.k"]},
                  {$arrayElemAt: 
                    ["$updateData", {$indexOfArray: ["$updateData.k", "$$this.k"]}]
                  },
                  "$$this"
                ]
              }
            }
          },
          "$newData"
        ]
      },
      newData: "$$REMOVE",
      updateData: "$$REMOVE"
    }
  },
  {$set: {
      marksObtained: {
        $map: {input: "$marksObtained", in: ["$$this"]}
      }
  }},
  {$set: {
      marksObtained: {
        $map: {
          input: "$marksObtained",
          in: {$arrayToObject: "$$this"}
        }
      }
  }}
])

playground 示例中查看它是如何工作的

項目的順序並不重要

db.collection.updateMany(
  {_id: 11},
  [
   {$set: {
      marksObtained: {$map: {
          input: "$marksObtained",
          in: {$first: {$objectToArray: "$$this"}}
      }},
      dataToUpdate: {$map: {
          input: [{chemistry: 13}, {zoology: 12}],
          in: {$first: {$objectToArray: "$$this"}}
      }}
  }},
  {$set: {
      marksObtained: {$concatArrays: [
          {$filter: {
              input: "$marksObtained",
              cond: {$not: {$in: ["$$this.k", "$dataToUpdate.k"]}}
          }},
          "$dataToUpdate"
      ]},
      dataToUpdate: "$$REMOVE"
  }},
  {$set: {
      marksObtained: {$map: {input: "$marksObtained", in: ["$$this"]}}
  }},
  {$set: {
      marksObtained: {$map: {input: "$marksObtained", in: {$arrayToObject: "$$this"}}}
  }}
])

playground 示例中查看它是如何工作的

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM