I know there are a bunch of gotcha's to look out for
I know there are a bunch of gotcha's to look out for
On the other hand, you could have an "articles" table and a "pages" table with the relationship:
articles 1 <--> 1..* pages
Each page's text is stored as a separate row in the "pages" table, along with the article ID and a page number. When a page is requested, only the page that matches the requested article's ID and the appropriate page number. The database schema here is more complex, but is likely to be more efficient.Whichever method you choose, the software will need to be able to transform the data between two formats: one suitable for the user interface, and one suitable for the database.
However you designed the UI, it would be possible to use either approach for the database. For example, suppose the UI used a single textarea and had a special notation for delimiting pages. For the first approach, the contents of the textarea can simply be stored in its own field. For the second approach, the software would take the contents of the textarea, split it into its individual pages, then store each page separately.
If the UI had separate textareas for each page, then for the first approach, the software combines the contents of the textareas and puts in delimiters. For the second approach, each textarea is mapped to their own database field.
I suppose some of combinations are easier for the programmer than others, but so long as the UI is designed to be easy to use, the user need never know how the article is actually represented in the database.
Essentially, once your rows are ordered, your next page is "rows > the last row on the previous page, limit <number of items to return>"
This has the fantastic benefit of being index-friendly, which OFFSET is not, and if you structure the URLs for the pages well, obvious what you're getting back. (e.g., if your records are by date, you might get /daily-reports?since=2015-01-04&limit=50 for the page that starts at 2015-01-04)
It's from an SQL site, but the concept is not specific to SQL. I'm working on an implementation of it for data stored in Mongo, at the moment.
limit = page_size
offset = (current_page - 1) * limit
For example, let's say you have a table with 1000 products in it, and you want to display page 3 with a page size of 50. Here's how you would write it: SELECT *
FROM products
LIMIT 50
OFFSET 100At which point you could assign each post in a topic an indexed `position` column that always increases from 0 within that topic, which lets you jump to arbitrary pages and parameterize perPage.
fromPos = (page - 1) * perPage
SELECT *
FROM posts
WHERE topic_id = $1
AND position >= fromPos
AND position < fromPos + perPage
ORDER BY id
For simpler needs, your "Next" button could load items that come after the last ID on the current page but with a LIMIT. <ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
<a href="/items?afterId={{ items[items.length - 1].id }}">Next</a>
SELECT *
FROM items
WHERE id > $afterId
LIMIT $perPage
That's fast and really easy.The top-level comment is asking for good ways to paginate. OFFSET works on localhost and with hundreds of rows. My solution is one that works in production once OFFSET fails you. For me, it was day 2.
You don't need to heapsort the entire dataset before you offset to a page, and there are engines capable of doing this.
I empirically know this was an issue back then. I also agree it's something a DB engine should handle, and I do hope that databases do a better job today.
So instead of calling out "premature optimization", maybe instead come with a practical example of a DB engine that properly handles this?
For NoSQL, specifically (only?) CouchDB, you start iterating from a given key (in SQL, this would effectively be "WHERE id >= $start ORDER BY id ASC LIMIT n").
This difference is why I would recommend, especially for paginated REST APIs, to provide links to the next/prev pages, so API clients can follow these links without thinking about how pagination is accomplished exactly. To prevent people from guessing the inner workings and working around your links, you can encode the parameters in a "cursor" (so instead of ?pagesize=20&page=10 you could have ?cursor=base64encode("20,10") (SQL) or ?cursor=base64encode("$startKey") (NoSQL)). IIRC Facebook does it this way. Encrypting or authenticating the cursor is IMHO overblown here, given cursor values can be easily validated and rejected if fiddled with.
Using an opaque cursor also gives you the ability to change how pagination works without breaking existing API clients.
If you plan for lots of data and many pages, think twice before outputting links to all pages (on a website) or outputting the total number of elements (in an API). COUNT(*) on InnoDB is slow and I heard it's not the quickest thing in NoSQL databases either.
http://use-the-index-luke.com/sql/partial-results/fetch-next...
edit: more on this via a slideshare presentation or pdf:
http://use-the-index-luke.com/blog/2013-07/pagination-done-t...
This is very efficient when your table is indexed by that field; you can seek directly to the next record.
Some database APIs do this for you!
I wrote up my experience implementing this on App Engine here, http://johntantalo.com/blog/paginating-with-bookmarks-in-app...
Forcing artificial page-breaks is abusive.
Yes, don't unnecessarily break up content. But most pages are going to include lists of some sort that you want to be paginated in some way.
If you look at HNs /new queue, there are submission IDs in the next links, so you can click "more" multiple times without seeing content multiple times, even if new submissions have pushed them down in the global list by now. Useful if there are many updates and new entries come in at the "top" of the list.