This is my query to fetch the latest message from each room:
SELECT MAX
( "Messages"."id" ) AS messageId,
"Rooms"."id"
FROM
"RoomUsers"
INNER JOIN "Rooms" ON "RoomUsers"."roomId" = "Rooms"."id"
INNER JOIN "Conversations" ON "Conversations"."roomId" = "Rooms"."id"
INNER JOIN "Messages" ON "Messages"."conversationId" = "Conversations"."id"
WHERE
"RoomUsers"."userId" = 51
GROUP BY
"Rooms"."id"
And the output could be something like this:
+----------+-------+
|messageId | id |
+----------+-------+
| 1 | 4 |
| 17 | 5 |
| 7 | 6 |
| 29 | 7 |
| 18 | 8 |
| 19 | 9 |
+----------+-------+
As you can see, this is just a small result set, but if I have more than 1000 rooms with messages, it could become a large dataset, and fetching it all at once isn’t efficient or necessary for the application.
So, I want to implement pagination using OFFSET and LIMIT.
For testing, I started with small pages.
I modified my query to paginate the data with 3 records in each page:
SELECT MAX
( "Messages"."id" ) AS messageId,
"Rooms"."id"
FROM
"RoomUsers"
INNER JOIN "Rooms" ON "RoomUsers"."roomId" = "Rooms"."id"
INNER JOIN "Conversations" ON "Conversations"."roomId" = "Rooms"."id"
INNER JOIN "Messages" ON "Messages"."conversationId" = "Conversations"."id"
WHERE
"RoomUsers"."userId" = 51
GROUP BY
"Rooms"."id"
LIMIT 3 OFFSET 0
After running this query, this is the output:
+----------+-------+
|messageId | id |
+----------+-------+
| 1 | 4 |
| 17 | 5 |
| 7 | 6 |
+----------+-------+
It’s okay just for this page. And if I add a page and change the offset to 1, this is the result:
+----------+-------+
|messageId | id |
+----------+-------+
| 17 | 5 |
| 7 | 6 |
| 29 | 7 |
+----------+-------+
And if I increase the offset’s number and change it to 2, this is the next result:
+----------+-------+
|messageId | id |
+----------+-------+
| 7 | 6 |
| 29 | 7 |
| 18 | 8 |
+----------+-------+
So, what is going on here?
I couldn’t understand the reason, and I need to paginate this data too!
What can I do, and how should I change it?
2
Answers
Assuming 3 rows per page the offset needs to be 0, 3 and 6.
Also, the order of result is undeterministic without an
order by
clause. Add:Postgres has non-embedded SQL
cursors
, and that’s pretty much exactly what they’re for. Demo:Run it once, and
fetch
the results page by page:When you’re done,
close
it:offset
would do.order by
.insensitive
) at all times, whereas anydelete
/insert
/update
applied to that table while you’re in the process of reading pages, whackslimit
/offset
out of order.WITH HOLD
persists the cursor even if you haveautocommit
on, or need to runcommit
s between fetches in the same session. The only way to stabiliselimit
/offset
-based pagination is to restrict it to a singlerepeatable read
transaction, or dump it somewhere and read the dump, emulating thecursor
behaviour ineffectively.WITH HOLD
resorts to caching only if youcommit
before closing.That being said,
cursor
is just a single notch up fromlimit
/offset
pagination – there are other ways.