node.jsmongodb-queryaggregation-frameworknode-modulesmonodevelop

Add a new item to a nested array and update the existing elements according to the inserted item


{
    "_id": {
        "$oid": "6200c86d083ef16be6dae5b1"
    },
    "type": "Hair_Length",
    "values": [
        {
            "name": "Bald / Clean Shaven",
            "value": "bald",
            "default": "yes"
        },
        {
            "name": "Crew Cut",
            "value": "crewcut"
        },
        {
            "name": "Short",
            "value": "Short"
        },
        {
            "name": "Medium",
            "value": "Medium"
        },
        {
            "name": "Long",
            "value": "Long"
        },
        {
            "name": "Chin Length",
            "value": "ChinLength"
        },
        {
            "name": "Shoulder Length",
            "value": "ShoulderLength"
        },
        {
            "name": "Dreadlocks",
            "value": "Dreadlocks"
        }
    ]
}

Good day guys! if I get name and value as json object then only I will update or add to this doc if incase we get "default":"yes" in addition to name and value then we need to update and delete if default is already exist I hope you got point

appreciate it guys tq


Solution

  • Since you know if your new item is a default item or not, before the query, it is better to adjust the query to the relevant case before executing it. But just for the sake of the challenge, this is a solution with one query only. I advise you to break it into two queries and use only one at each case.

    db.collection.aggregate([
      {
        $match: {type: "Hair_Length"}
      },
      {
        $addFields: {
          newValue: {"name": "short hair", "value": "shorthair", "default": "yes"},
          values: {$filter: {input: "$values",
              as: "item",
              cond: {$ne: ["$$item.value", "shorthair"]}
            }
          }
        }
      },
      {
        $facet: {
          newIsDefault: [
            {
              $project: {"values.name": 1, "values.value": 1, type: 1, newValue: 1}
            }
          ],
          newIsRegular: [
            {$project: {values: 1, type: 1, newValue: 1}
            }
          ]
        }
      },
      {
        $project: {newIsDefault: {$arrayElemAt: ["$newIsDefault", 0]},
                   newIsRegular: {$arrayElemAt: ["$newIsRegular", 0]}}
      },
      {
        $replaceRoot: {
          newRoot: {
            $cond: [{$eq: ["$newIsDefault.newValue.default", "yes"]},
              "$newIsDefault",
              "$newIsRegular"
            ]
          }
        }
      },
      {
        $project: {
          values: {$setUnion: [["$newValue"], "$values"]},
          type: 1
        }
      },
      {$merge: {into: "collection"}}
    ])
    

    You can see it work on the playground on one case and on another case.

    The main idea is that there are two main cases: the new item is default or not. On both cases you start by filtering out from the list the element with value matches the new item if they are there. Then, if the new item is default you remove the default field from the items in the list. Only now we can insert the new item to the list.

    Query: First you add your new item using $addFields and $filter out the item if exists, then you create the documents for the two cases using $facet. one of them to the case where the new item is default, and on e for the case it is not. then you choose which one to use, using $cond, and add the new item to it using $setUnion. Last step is $merge to replace the new doc with the old one.