Problem: I'm building a RESTful API on top of Postgres that includes the following parameters:
- An identifier
<id>
for a particular record in a table - Filter and sort parameters (optional)
count
or page size (optional)
For example: /presidents/16?filter=!isAlive&sort=lastName,givenNames&count=5
The API returns count
(or possibly fewer) records that include the record specified by <id>
and the offset
and count
of records returned.
In the example above, the result might look like:
{
"count": 5,
"offset": 20,
"records": [
{ "id": 17, "givenNames": "Andrew", "lastName": "Johnson", "isAlive": false },
{ "id": 36, "givenNames": "Lyndon B.", "lastName": "Johnson", "isAlive": false },
{ "id": 35, "givenNames": "John F.", "lastName": "Kennedy", "isAlive": false },
{ "id": 16, "givenNames": "Abraham", "lastName": "Lincoln", "isAlive": false },
{ "id": 4, "givenNames": "James", "lastName": "Madison", "isAlive": false }
]
}
Current Solution: My current approach is to make three calls in serial (composing them into a single, nested query, but efficiency question remains) and I'm looking for a better approach.
Get the record referred to by target
<id>
, used in query #2.- e.g.
select * from presidents where id = 16
- e.g.
Get the offset of the target
<id>
- e.g.
select count(*) from presidents where lastName < 'Lincoln' and givenNames < 'Abraham' and not isAlive order by lastName, givenNames, id
- e.g.
After computing the appropriate
offset
andlimit
, using the passed (or default)count
and thecount(*)
from #2, retrieve the page of records.- e.g.
select * from presidents where not isAlive order by lastName, givenNames, id offset 20 limit 5
- e.g.
Updated SQL
I took what @ErwinBrandstetter provided in his answer below, which was close to what I was looking for in way of a monster statement, and changed it into this:
WITH prez AS (SELECT lastName, givenNames, id, isAlive FROM presidents WHERE not isAlive),
cte AS (SELECT * FROM prez WHERE id = 16),
start AS (SELECT (COUNT(*)/5)*5 as "offset" FROM prez WHERE (lastName, givenNames, id, isAlive) < (TABLE cte))
SELECT row_to_json(sub2) AS js
FROM (
SELECT (SELECT * FROM start) AS "offset"
, count(*) AS "count"
, array_agg(sub1) AS records
FROM (
SELECT * from prez
ORDER BY lastName, givenNames, id
OFFSET (SELECT * from start) LIMIT 5
) sub1
) sub2;
Is there an easier way in Postgres to determine the the offset of a given record for a given query?
Related questions: