As part of implementing a GraphQL API I'm attempting to provide before/after pagination. For example, to return LIMIT records that come after a record with a given ID. This has to work on an arbitrary SELECT statement with arbitrary JOIN, WHERE and ORDER BY clauses already in place.
The benefit of this kind of pagination over just using page numbers is that it can more closely return an expected page of results when the underlying data is changing.
If I can get this working for after I can also make it work for before by inverting the ORDER BY clause, so here I'll focus just on the after condition.
What's the easiest or most efficient way to modify a given SELECT statement to accomplish this?
My first thought was to add an AND condition to the WHERE clause restricting results to those with column values from the ORDER BY clause that are greater than or equal to their values in the record with the given ID. But this doesn't seem to work, because there is no expectation of uniqueness in the ORDER BY clause columns, so there's no way to know where the target record will fall in the results, and therefore no way to know how to set the LIMIT to return the correct number of records.
Another approach is to first discover the offset of the target record within the initial SELECT statement, and then to add a LIMIT offset+1, limit
clause to the initial SELECT statement with the discovered offset.
MySQL has no row_count()
function or similar, but row numbers can be added like this:
SELECT @rownum:=@rownum+1 ‘rank’, t.*
FROM my_table t, (SELECT @rownum:=0) r ORDER BY field2;
Then the above can be used as a subquery to fetch the rank of the target record, e.g.
SELECT rank FROM (SELECT @rownum...) WHERE id = 42
And then using that rank as the offset for the final query:
SELECT ... LIMIT (rank + 1), 100
Possibly this can be done as a single query with multiple subqueries, e.g.
SELECT ... LIMIT (SELECT rank from (SELECT @rownum...) ...) + 1, 100
But this three query approach seems like an elaborate and not very rapid way to perform a very frequently used operation, putting a higher load on our database servers than we would prefer.
Is there a better way to do this?
Edit: A specific example was requested. Say I want to get a page of 2 articles from a table of 10 articles. We'll paginate this query:
select id, title from articles order by title desc
The table:
id, title
1, "a"
3, "b"
4, "c"
6, "d"
7, "e"
8, "f"
9, "g"
10, "h"
11, "i"
12, "k"
So when requesting the page after id 6
the correct records would be 4, "c"
and 3, "b"
. This needs to work for arbitrary WHERE and ORDER BY clauses.