arraystypescriptmongodbdatabase-migration

How to update a specific Array item by index, and copy the value of another field, in MongoDB?


I have many records in a MongoDB database, like this:

[
  {
    rootName: "AAA",
    outerItem: {
      innerItems: [
        {
          name: "A"
        },
        {
          name: null
        },
        {
          name: "C"
        }
      ]
    }
  }
]

I would like to copy the rootName field of each record into an outerItem.innerItems[1].newField only, using a named index position of 1 (or looking up the correct index in some other way).

What I expect is for each record to look like this, after the code is run:

[
  {
    "rootName": "AAA",
    "outerItem": {
      "innerItems": [
        { // 0
          "name": "A",
        },
        { // 1
          "name": null,
          "newField": "AAA" // New item should be added here only (array index 1)
        },
        { // 2
          "name": "C",
        }
      ]
    }
  }
]

I've tried using this code:

db.collection.update({},
[
  {
    "$set": {
      "outerItem.innerItems": {
        $map: {
          input: "$outerItem.innerItems",
          in: {
            $mergeObjects: [
              "$$this",
              {
                newField: "$rootName"
              }
            ]
          }
        }
      }
    }
  }
],
{
  "multi": false,
  "upsert": false
})

MongoDB Playground Link: https://mongoplayground.net/p/QhN1qAbWFwZ

But I end up with this:

[
  {
    "_id": ObjectId("5a934e000102030405000000"),
    "outerItem": {
      "innerItems": [
        {
          "name": "A",
          "newField": "AAA"
        },
        {
          "name": null,
          "newField": "AAA"
        },
        {
          "name": "C",
          "newField": "AAA"
        }
      ]
    },
    "rootName": "AAA"
  }
]

The problem is that the newField is copied to all the array items, when I only want it copied to 1 specific array item of index 1.

I tried to modify the code to use a condition of array index 1, but the following code does not work. It produces an error:

db.collection.update({},
[
  {
    "$set": {
      "outerItem.innerItems": {
        $map: {
          input: "$outerItem.innerItems",
          in: {
            $cond: [
              {
                $eq: [
                  "$$item.id",
                  1
                ]
              }
            ],
            $mergeObjects: [
              "$$this",
              {
                newField: "$rootName"
              }
            ]
          }
        }
      }
    }
  }
],
{
  "multi": false,
  "upsert": false
})

MongoDB Playground link: https://mongoplayground.net/p/ckgDCUN1XrR

This version also doesn't work:

db.collection.update({
  "outerItem.innerItems": {
    $elemMatch: {
      "name": null
    }
  }
},
[ ... ]

I can't figure out how to get it working. I have used the following guides so far, but I still cannot find a working solution for this scenario.

How do I get it working? The final code is intended to be used in Node.js TypeScript code, probably using something like:

await queryRunner.updateMany('name_items', {}, [ { $set: ... } ] );

Solution

  • You can do this with some $slice'ing and $concatArrays:

    1. Get the first element with slice(<array>, 0, 1) (start index=0, n=1) (or use $first and wrap it in [...])
    2. Get the second element with slice(<array>, 1, 1) (start index=1, n=1) and just the first of this single-element slice; and merge it with newField.
    3. Get the rest of the array with slice(<array>, 2, size-of-array-or-large-numebr)
    db.collection.update({},
    [
      {
        $set: {
          "outerItem.innerItems": {
            $concatArrays: [
              { $slice: ["$outerItem.innerItems", 0, 1] },
              [
                {
                  $mergeObjects: [
                    { $first: { $slice: ["$outerItem.innerItems", 1, 1] } },
                    { newField: "$rootName" }
                  ]
                }
              ],
              { $slice: ["$outerItem.innerItems", 2, { $size: "$outerItem.innerItems" }] }
            ]
          }
        }
      }
    ])
    

    Mongo Playground