Search code examples
mongodbmongoosesubdocument

Move element in the same array or in other in mongodb


i've this document schema in my mongodb:

{
  lessons: [
    {
      _id: 'lesson1',
      title: 'lesson1',
      contents: [
        {
          _id: 'content1',
          title: 'content1',
        },
        {
          _id: 'content2',
          title: 'content2',
        },
        {
          _id: 'content3',
          title: 'content3',
        }
      ]
    },
    {
      _id: 'lesson2',
      title: 'lesson2',
      contents: [
        {
          _id: 'content1',
          title: 'content1',
        },
        {
          _id: 'content2',
          title: 'content2',
        },
        {
          _id: 'content3',
          title: 'content3',
        }
      ]
    }
  ]
}

and i want to move the entire object with _id content1 of object with _id lesson1 in position 3 of the same array OR move that object in position 2 of object with id lesson2 having only the id of content to move, the origin lesson id where the content is and and destination lesson id.

The scenario is:

I have a originLessonId and a targetLessonId (could be the same id), a contentId that i will move from origin lesson to the lesson target and an index that decides the position where i will move the content object.

For example:

i have this random move request { originLessonId: 'lesson1', targetLessonId: 'lesson1', contentId: 'content1', index: 2 }

So: i need to move content1 object from lesson1 to lesson1 (the same lesson object in this case) from position where it is to position 2.

The result will like:

{
      lessons: [
        {
          _id: 'lesson1',
          title: 'lesson1',
          contents: [
            {
              _id: 'content2',
              title: 'content2',
            },
            {
              _id: 'content3',
              title: 'content3',
            },
            {
              _id: 'content1',
              title: 'content1',
            },
          ]
        },
        {
          _id: 'lesson2',
          title: 'lesson2',
          contents: [
            {
              _id: 'content1',
              title: 'content1',
            },
            {
              _id: 'content2',
              title: 'content2',
            },
            {
              _id: 'content3',
              title: 'content3',
            }
          ]
        }
      ]
    }

Or { originLessonId: 'lesson1', targetLessonId: 'lesson2', contentId: 'content1', index: 1 }.

In this case: i need to move content1 object from lesson1 to lesson2 (different lesson object in this case) from position where it is to position 1 of other lesson object.

the result is like:

{
      lessons: [
        {
          _id: 'lesson1',
          title: 'lesson1',
          contents: [
            {
              _id: 'content2',
              title: 'content2',
            },
            {
              _id: 'content3',
              title: 'content3',
            }
          ]
        },
        {
          _id: 'lesson2',
          title: 'lesson2',
          contents: [
            {
              _id: 'content1',
              title: 'content1',
            },
            {
              _id: 'content1',
              title: 'content1',
            },
            {
              _id: 'content2',
              title: 'content2',
            },
            {
              _id: 'content3',
              title: 'content3',
            }
          ]
        }
      ]
    }

The request values of query could be different every time.. so needs a query that could manage different cases (like these two).. I tried many ways but i cannot make it with these conditions..

Thank you!


Solution

  • One option is to use update with pipeline in two stages:

    1. $set the item that needs to be removed and remove it from the lessons.
    2. Use $reduce and $range to insert it to the right place.
    db.collection.updateOne(
      {}, // _id: docId
      [
      {$set: {
          item: {$reduce: {
              input: "$lessons",
              initialValue: {},
              in: {$mergeObjects: [
                  "$$value",
                  {$first: {$filter: {
                        input: "$$this.contents",
                        as: "content",
                        cond: {$eq: ["$$content._id", contentId]}
                  }}}
              ]}
          }},
          lessons: {$map: {
              input: "$lessons",
              as: "lesson",
              in: {$mergeObjects: [
                  "$$lesson",
                  {contents: {$cond: [
                        {$eq: ["$$lesson._id", originLessonId]},           
                        {$filter: {
                            input: "$$lesson.contents",
                            cond: {$ne: ["$$this._id", contentId]}
                        }},
                        "$$lesson.contents"
                  ]}}
              ]}
          }}
      }},
      {$set: {
          item: '$$REMOVE',
          lessons: {$map: {
              input: "$lessons",
              as: "lesson",
              in: {$mergeObjects: [
                  "$$lesson",
                  {contents: {$cond: [
                        {$eq: ["$$lesson._id", targetLessonId]},
                        {$reduce: {
                            input: {$range: [
                                0,
                                {$add: [{$size: "$$lesson.contents"}, 1]}
                            ]},
                            initialValue: [],
                            in: {$concatArrays: [
                                "$$value",
                                {$cond: [
                                    {$eq: ["$$this", index]},
                                    ["$item"],
                                    [{$arrayElemAt: ["$$lesson.contents", "$$this"]}]
                                ]}
                            ]}
                        }},
                        "$$lesson.contents"
                  ]}}
              ]}
          }}
      }}
    ])
    

    See how it works on the playground example

    The double nested array makes it a bit clumsy. Consider keeping each lesson as a document if possible.