Search code examples
mongodbpaginationmongodb-queryaggregate

MongoDb - Return bulks of N elements from the end of a nested array looping backwards at each call (pagination)


I have a collection of categories, and each category has its array of _id from a different collection. My goal is to create an infinite scrolling by giving N records every time but from the end (the last record is always the most recent).

Category

{ 
    "_id" : ObjectId("625167ce3859b8465ccf69dc"), 
    "name" : {
        "en" : "Category #1"
    }, 
    "tracks" : [
        ObjectId("627f8475c229513838eed070"), 
        ObjectId("627f84b4c229513838eed074"), 
        ObjectId("6280ef548b97521c1f462266"), 
        ObjectId("6280ef68d147e83534f4ca03"), 
        ObjectId("6280ef6ad147e83534f4ca07"), 
        ObjectId("6280ef6bd147e83534f4ca0b"),
        // and so on..
    ]
}

Code:

categories
    .aggregate([
      {
        $match: {
          _id: ObjectId(categoryId),
        },
      },
      {
        $project: {
          tracks: {
            $slice: ["$tracks", -N],
          },
        },
      },
      {
        $lookup: {
          from: "tracks",
          as: "tracks",
          localField: "tracks",
          foreignField: "_id",
          pipeline: [{ $sort: { uploadedDate: -1 } }],
        },
      },
    ])

So basically I slice the array, to get last N elements and then look them up on tracks collection. But I want to get N records from the end between specific range. For example, if I have 100 records, so giving the iteration number 0, and bulk size: 25, the last 25 elements will be returned (index 75-99). On iteration 1, the next (moving backwards) 25 elements will be returned (index 50-74), and so on...


Solution

  • You can so something like:

    EDIT: to support edge cases:

    db.collection.aggregate([
      {
        $match: {_id: ObjectId("625167ce3859b8465ccf69dc")}
      },
      {
        $addFields: {
          avilableCount: {$max: [
            {$subtract: [{$size: "$tracks" }, bulkSize * iterations]},  
          0]}
        }
      },
       {
        $project: {
          tracks: {
            $cond: [{$eq: ["$avilableCount",  0]},
              [],
              {$slice: ["$tracks", {
                    $max: [
                      {$subtract: [{$size: "$tracks"}, bulkSize * (iterations + 1)]}, 0]},
                  {$min: [bulkSize , "$avilableCount"]}
                ]
              }
            ]
          }
        }
      }
    ])
    

    Playground example

    This will return you a bulk of tracks with size bulkSize, starting from the end of the list for the first iteration and going backwards along the iterations. So if you have 100 tracks with index 0-99 and the bulkSize is 25, you will get tracks 75-99 for the first iteration, 50-74 for the 2nd iteration 25-49 for the 3rd... The avilableCount allows us to look at the edge cases: In the edge case where only a part of the bulk size is available it will return this part, and if non is available it will return [].