How to incorporate paginating in MongoDB operations

It's a well-known fact that using skip for pagination can lead to performance issues, especially as the data set grows larger. One workaround is to leverage natural ordering using the _id field:

//Page 1
db.users.find().limit(pageSize);
//Get the id of the last document on this page
last_id = ...

//Page 2
users = db.users.find({'_id'> last_id}). limit(10);

The challenge I'm facing is that as a newbie to MongoDB, I'm uncertain about the best approach to retrieve this crucial last_id

Answer №1

The idea being discussed can be referred to as "forward paging". One important distinction is that unlike the use of .skip() or .limit() modifiers, this method does not allow for going back to a previous page or jumping directly to a specific page without significant effort in storing "seen" or "discovered" pages. If your goal involves this type of "links to page" paging, then it is recommended to stick with the .skip() and .limit() approach despite any performance limitations.

If you are open to only "moving forward", here is the fundamental concept:

db.junk.find().limit(3)

{ "_id" : ObjectId("54c03f0c2f63310180151877"), "a" : 1, "b" : 1 }
{ "_id" : ObjectId("54c03f0c2f63310180151878"), "a" : 4, "b" : 4 }
{ "_id" : ObjectId("54c03f0c2f63310180151879"), "a" : 10, "b" : 10 }

These are the first three items on your initial page. You can iterate over the cursor using the following code snippet:

var lastSeen = null;
var cursor = db.junk.find().limit(3);

while (cursor.hasNext()) {
   var doc = cursor.next();
   printjson(doc);
   if (!cursor.hasNext())
     lastSeen = doc._id;
}

This loop iterates the cursor, performing actions, and stores the value of the present _id under lastSeen when the end of the cursor is reached:

ObjectId("54c03f0c2f63310180151879")

In subsequent iterations, you feed this stored _id value back into the query:

var cursor = db.junk.find({ "_id": { "$gt": lastSeen } }).limit(3);

while (cursor.hasNext()) {
   var doc = cursor.next();
   printjson(doc);
   if (!cursor.hasNext())
     lastSeen = doc._id;
}

{ "_id" : ObjectId("54c03f0c2f6331018015187a"), "a" : 1, "b" : 1 }
{ "_id" : ObjectId("54c03f0c2f6331018015187b"), "a" : 6, "b" : 6 }
{ "_id" : ObjectId("54c03f0c2f6331018015187c"), "a" : 7, "b" : 7 }

This process repeats until no further results can be obtained.

This serves as a basic guide for natural order paging like _id. For other scenarios, the process becomes more intricate. Review the following example:

{ "_id": 4, "rank": 3 }
{ "_id": 8, "rank": 3 }
{ "_id": 1, "rank": 3 }    
{ "_id": 3, "rank": 2 }

To split these entries into two pages sorted by rank, you must keep track of what has been "seen" and exclude those records. For the first page:

var lastSeen = null;
var seenIds = [];
var cursor = db.junk.find().sort({ "rank": -1 }).limit(2);

while (cursor.hasNext()) {
   var doc = cursor.next();
   printjson(doc);
   if ( lastSeen != null && doc.rank != lastSeen )
       seenIds = [];
   seenIds.push(doc._id);
   if (!cursor.hasNext() || lastSeen == null)
     lastSeen = doc.rank;
}

{ "_id": 4, "rank": 3 }
{ "_id": 8, "rank": 3 }

In the next iteration, you want ranks less than or equal to the last seen rank while excluding previously viewed documents. Employ the $nin operator for this purpose:

var cursor = db.junk.find(
    { "_id": { "$nin": seenIds }, "rank": "$lte": lastSeen }
).sort({ "rank": -1 }).limit(2);

while (cursor.hasNext()) {
   var doc = cursor.next();
   printjson(doc);
   if ( lastSeen != null && doc.rank != lastSeen )
       seenIds = [];
   seenIds.push(doc._id);
   if (!cursor.hasNext() || lastSeen == null)
     lastSeen = doc.rank;
}

{ "_id": 1, "rank": 3 }    
{ "_id": 3, "rank": 2 }

The number of "seenIds" retained depends on the granularity of your results where this value is likely to change. In this case, you can reset the seenIds list if the current rank differs from the lastSeen, preventing unnecessary growth.

These are the core concepts of "forward paging" for practical learning purposes.

Answer №2

A straightforward method for incorporating pagination within MongoDB

  // Pagination
  const currentPage = parseInt(req.query.page, 10) || 1;
  const itemsPerPage = parseInt(req.query.limit, 10) || 25;
  const startingIndex = (currentPage - 1) * itemsPerPage;
  const endingIndex = currentPage * itemsPerPage;
  query = query.skip(startingIndex).limit(itemsPerPage);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for optimizing SEO by efficiently indexing data from client-side API requests

I'm not seeking a step-by-step tutorial on how to set this up. I'm genuinely curious about what's achievable and what's not. Within my Angular 6 application, I am exploring the implementation of server-side content loading for better i ...

A guide to using JavaScript to retrieve a CSV file in HTML and store its contents in an array

I am currently working in AngularJs with the objective of uploading a file and saving it as an array using JavaScript. Below is my HTML code: <input type="file" id="uploadFile" name="uploadFile" accept=".csv" required /> <button ng-click="proces ...

Encountering Issues with Laravel AJAX POST Request (500 Internal Server Error)

I have been struggling with this issue for hours, going through numerous examples and StackOverflow answers without any success. This is my first time working with AJAX, although I do have considerable experience with Laravel. The problem I am facing is a ...

Quickly select a stand-out team from a pool of candidates

In my current scenario, I am dealing with a database of 150 players including their names, IDs (UUIDs), and team names. Users have the ability to create teams consisting of 10 players, and there is no limit on how many teams they can create. Essentially ...

Adjust the height automatically in extjs hbox layout with this quick guide

When I try to dynamically set data in this layout, the layout doesn't resize properly and the final result looks like this. This is the code I'm currently using: win = Ext.create('widget.window', { title: 'Layout Window&apo ...

Ways to detect scrolling activity on the v-data-table module?

Are you looking for a way to detect scrolling events on the v-data-table component in Vuetify framework? I am referring to the scenario where the table has a fixed height, causing the table body to scroll. <v-data-table fixed-header :height=400 : ...

One requirement for a directive template is that it must contain only a single root element, especially when using the restrict option to E

I am currently managing an older AngularJS application (v1.3.8). Why is the demo application showing me this error? The directive 'handleTable' template must have only one root element. sandbox.html <!DOCTYPE html> <html> <he ...

Error encountered during Heroku deployment: "sh: 1: tailwind: not found"

package.json: "devDependencies": { "tailwindcss": "^0.7.4" }, "scripts": { "tailwind:css": "tailwind build src/css/tailwind.src.css -c tailwind.js -o src/css/tailwind.css", "start": "npm run tailwind:css && react-scripts start", ...

Establishing a minimum date based on the date selected in the earlier datepicker

My webpage features two date pickers, one for startdate and the other for enddate. The current setup requires that the second datepicker remains inactive until a change is made to the first one. The datepicker for enddate is initially set with the startin ...

Achieving successful content loading through AJAX using the .load function

Our website features a layout where product listings are displayed on the left side, with the selected product appearing on the right side (all on the same page). Initially, when the page loads, the first product is shown on the right. Upon clicking a new ...

Combine 2 queries using RTK Query

When chaining queries, how can I ensure that the 2nd query only runs after the 1st query has returned a parameter needed for the 2nd? const { data: user } = useGetUserQuery(); The user object contains an ID which is required to run the next query: const { ...

The Challenge of Referencing Javascript Files (including jQuery)

Previously, I had the following code snippet: <head> <script src="/Scripts/jquery-1.3.2.min.js" type="text/javascript"></script> <script type="text/javascript"> var optPrompt = "- Select One -"; var subCats ...

the function is not correctly displaying elements on the page through JavaScript

I'm facing an issue with the code provided - while the Navbar is being rendered, the h1 and img elements are not displaying anything. Upon inspection, it seems like the values inside these elements are not available. I tried debugging by logging to th ...

Dealing with multiple parameters using React Router

I work with two main components: the AddList component and the DetailList component. The functionality I have set up involves a list of AddList components, each containing a button in every li element. When a user clicks on any button, the corresponding ID ...

Count the number of documents in a CommandCursor using Mongodb's count()

Currently, I am executing a search using this aggregate method and would like to fetch the total count for pagination purposes. results = mongo.db.perfumes.aggregate( [ {"$match": {"$text": {"$search": db_query}}}, { "$look ...

One-way communication between two clients using Socket.io

While working on a basic socket.io application using React and Express, I've encountered an issue where two clients are facing difficulties in sending data to each other. For instance: Player 1 connects to the server followed by Player 2. Player 1 ...

What is the solution to the error "modal is not defined" in Vue.js?

Hey guys, I need some help with an error that popped up: [Vue warn]: Error in v-on handler: "TypeError: $(...).modal is not a function". The issue is with the modal function Below is the code snippet from my welcome.blade.php: <body> &l ...

I am looking to tally up multiple inputs in JavaScript and then submit them to a MySQL database

I have encountered an issue with my code. It works fine for the initial input in the original HTML code, but when I add a new row, it fails to count them. Additionally, I need to display a PHP variable in JavaScript and ultimately post the added rows to a ...

The AngularJS templates' use of the ternary operator

Is there a way to implement a ternary operation in AngularJS templates? I am looking for a way to apply conditionals directly in HTML attributes such as classes and styles, without having to create a separate function in the controller. Any suggestions wo ...

Wait to execute until the external script in Vue.js has finished loading

How can I trigger the rendering of a recaptcha after a Vue.js component has mounted? It functions correctly on initial load and reload, but throws an error when navigating away to a different url and using the browser back button. Here is how it's se ...