codedump
codedump

Reputation: 387

Move data from one array to another array in the same document

I need to move an array element from one array to another array within the same document and delete the element from the original array it was moved from.

// Document
{
   _id: ObjectId("67dc90594947be000838f7a7"),
   persons: [
        {
           personId: "61cd90594947be000838f7c1",
           name: "John Doe",
           employment: [
               {
                  employmentId: "61cd9059494abe000838f7c8",
                  type: "Full time",
                  salary: 1010101
               }
           ]
        },
        {
           personId: "61cd90594947be000838f7c2",
           name: "Jane Austin",
           employment: [
               {
                  employmentId: "61cd9059494abe000738f7c1",
                  type: "Part time",
                  salary: 11011111
               }
           ]
        },
   ]
}

I need to move one of the employment element from John Doe to Jane Austin and both of those persons are in the same document. And I need to delete that moved element from John Doe. employmentId is used to identify which employment to move. And personId is used to identify from which person to move to which person.

Example payload

const updatePayload = {
   fromPerson: "61cd90594947be000838f7c1",
   toPerson: "61cd90594947be000838f7c2",
   employmentId: "61cd9059494abe000838f7c8",
}

Expected Result

// employment from John Doe is moved to Jane Austin
{
   _id: ObjectId("67dc90594947be000838f7a7"),
   persons: [
        {
           personId: "61cd90594947be000838f7c1",
           name: "John Doe",
           employment: [ ]
        },
        {
           personId: "61cd90594947be000838f7c2",
           name: "Jane Austin",
           employment: [
               {
                  employmentId: "61cd9059494abe000738f7c1",
                  type: "Part time",
                  salary: 11011111
               },
               {
                  employmentId: "61cd9059494abe000838f7c8",
                  type: "Full time",
                  salary: 1010101
               }
           ]
        },
   ]
}

Upvotes: 1

Views: 210

Answers (1)

Tom Slabbaert
Tom Slabbaert

Reputation: 22276

Here is a working sample for Mongo version 4.4, our strategy will be to first match the employment we want to move, then update each of the persons accordingly, I will say that doing this in Mongo v5.0+ will be much cleaner as we could use $getField, this would make the first two stages of the pipelined update much simpler.

With that said here is the code sample:

const updatePayload = {
   fromPerson: "61cd90594947be000838f7c1",
   toPerson: "61cd90594947be000838f7c2",
   employmentId: "61cd9059494abe000838f7c8",
}

db.collection.update({},
[
  {
    $set: {
      tmpPerson: {
        $arrayElemAt: [
          {
            $filter: {
              input: "$persons",
              as: "person",
              cond: {
                $eq: [
                  "$$person.personId",
                  updatePayload.fromPerson
                ]
              }
            }
          },
          0
        ]
      }
    }
  },
  {
    $set: {
      tmpEmployment: {
        $arrayElemAt: [
          {
            $filter: {
              input: "$tmpPerson.employment",
              as: "employment",
              cond: {
                $eq: [
                  "$$employment.employmentId",
                  updatePayload.employmentId
                ]
              }
            }
          },
          0
        ]
      }
    }
  },
  {
    $set: {
      persons: {
        $map: {
          input: "$persons",
          as: "person",
          in: {
            $switch: {
              branches: [
                {
                  case: {
                    $eq: [
                      "$$person.personId",
                      updatePayload.fromPerson
                    ]
                  },
                  then: {
                    $mergeObjects: [
                      "$$person",
                      {
                        employment: {
                          $filter: {
                            input: "$$person.employment",
                            as: "employment",
                            cond: {
                              $eq: [
                                "$$employment.employmentId",
                                updatePayload.employmentId
                              ]
                            }
                          }
                        }
                      }
                    ]
                  }
                },
                {
                  case: {
                    $eq: [
                      "$$person.personId",
                      updatePayload.toPerson
                    ]
                  },
                  then: {
                    $mergeObjects: [
                      "$$person",
                      {
                        employment: {
                          $concatArrays: [
                            "$$person.employment",
                            [
                              "$tmpEmployment"
                            ]
                          ]
                        }
                      }
                    ]
                  }
                }
              ],
              default: "$$person"
            }
          }
        }
      }
    }
  },
  {
    $unset: [
      "tmpPerson",
      "tmpEmployment"
    ]
  }
])

Mongo Playground

One last thing to note is that this pipeline assumes that the input exists properly, otherwise it will add null to the employment array. if this is not the case then you should add some protection.

Upvotes: 1

Related Questions