2

I'm trying to upsert a specific array element in a MongoDB document.

this is an example of a document from my collection:

{
  "_id":3,
  "name":alex,
  "array": [
    {
      "_id":1,
      "a":30,
      "b":60
    },
    {
      "_id":2,
      "a":40,
      "b":80
    }
  ]
}
  • In case where "array._id" exist I want to update its "a" value without changing "b" value. Output example:
{
  "_id":3,
  "name":alex,
  "array": [
    {
      "_id":1,
      "a":90,
      "b":60
    },
    {
      "_id":2,
      "a":40,
      "b":80
    }
  ]
}
  • In case where "array._id" doesn't exist I want to create such entry with the given "_id" and "a" values in "array". Output example:
{
  "_id":3,
  "name":alex,
  "array": [
    {
      "_id":1,
      "a":30,
      "b":60
    },
    {
      "_id":2,
      "a":40,
      "b":80
    },
    {
      "_id":4,
      "a":50,
    }
  ]
}
  • In case where "array" doesn't exist I want to create it and insert to it an entry with the given "_id" and "a" values. Output example:
{
  "_id":3,
  "name":alex,
  "array": [
    {
      "_id":1,
      "a":30,
    }
  ]
}

1 Answer 1

2

One option is to use update with pipeline:

  1. Use $reduce to iterate over the array and change the item if it is there (using $mergeObjects). Use $ifNull to handle the case where the array do not exists.
  2. Use $concatArrays with $cond to add the item if it does not exist after the first stage.
const obj = {"_id": 1, "a": 90};
db.collection.update(
  {"_id":3},
  [{$set: {array: {
        $reduce: {
          input: {$ifNull: ["$array", [obj]]},
          initialValue: [],
          in: {$concatArrays: [
              "$$value",
              {$cond: [
                  {$eq: ["$$this._id", obj._id]},
                  [{$mergeObjects: ["$$this", obj]}],
                  ["$$this"]
              ]}
          ]}
        }
  }}},
  {$set: {
      array: {$concatArrays: [
          "$array",
          {$cond: [
              {$in: [obj._id, "$array._id"]},
              [],
              [obj]
          ]}
      ]}
  }}]
)

See how it works on the playground example

If the order of the items in the array is not important you can simplify it to one stage using $filter:

  1. Add the new item merged with the old one if exists, to an array where the item is filtered out.
const obj = {"_id": 1, "a": 90};
db.collection.update(
  {"_id":3},
  [{$set: {array: {
        $concatArrays: [
          {$filter: {
              input: {$ifNull: ["$array", []]},
              cond: {$ne: ["$$this._id", obj._id]}
          }},
          [{$mergeObjects: [
                {$first: {$filter: {
                      input: "$array",
                      cond: {$eq: ["$$this._id", obj._id]}
                }}},
                obj
          ]}]
        ]
  }}]
)

See how it works on the playground example

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.