Move element in the same array or in other in mongodb

46 views Asked by At

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!

1

There are 1 answers

1
nimrod serok On BEST ANSWER

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.