Reputation: 8198
I need to find the page number of a record in a query. I have offset and limit, but will also need the row number to calculate this.
Given a query object, and a record ID, how could I find the row number?
row number should be relative to the query table. limit and offset will be applied afterwards.
Any help appreciated...
Upvotes: 1
Views: 4000
Reputation: 6189
After a long searching, I found the following MySQL solution:
SELECT d.myRowSerial
FROM (
SELECT *, @rownum:=@rownum + 1 AS myRowSerial
FROM myTable, (SELECT @rownum:=0) AS nothingButSetInitialValue
WHERE 1=1 -- Optional: filter if required, otherwise, omit this line;
ORDER BY AnyColumn -- Apply the order you like;
) d
WHERE d.myColumn = 'Anything'; -- If you like to limit it to only
-- for any specific row(s), similar to the *MAIN query.
If you need the page number too, then just change the 1st line above like this:
SELECT d.myRowSerial, FLOOR((d.myRowSerial-1)/10) AS pageNumber
-- Say, 10 is per page;
You will have pageNumber==0 for page 1, and pageNumber=1 for page 2, .....
Upvotes: 0
Reputation: 8198
I've looked long and hard, and after 6hrs found a solution.
I use Mysql so I believe the only way I can do this is with @ variables.
Here is how I implemented it into SQLAlchemy:
qry = ...
sel = select([qry.c.id, "@rownum := @rownum + 1 as rownumber"])
conn = sel.engine.connect()
conn.execute("SET @rownum:=0")
res = conn.execute(sel)
@rownum
will increment with each row. we can add more columns from qry
on the 2nd line if we wanted to. res
is the final table with the rownumber
column, where we can call res.fetchall()
to return a list
Upvotes: 0
Reputation: 75277
It's a little awkward to get the "page number" of the record going backwards like that, usually a system that wants to bounce from the "detail" of a record back to the "paginated view of all of them" would just carry along the page number to the "detail" page.
But assuming you're working with a capable database you can get row numbers out of an arbitrary SELECT statement using a window function, which you can get with a DB like Postgresql, SQL Server or Oracle (notably not MySQL or SQLite).
Using PG we can start with some data:
test=> create table data(id SERIAL primary key, value varchar(20));
NOTICE: CREATE TABLE will create implicit sequence "data_id_seq" for serial column "data.id"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "data_pkey" for table "data"
CREATE TABLE
test=> insert into data (value) values ('d1'), ('d2'), ('d3'), ('d4'), ('d5'), ('d6'), ('d7'), ('d8'), ('d9'), ('d10'), ('d11'), ('d12'), ('d13'), ('d14'), ('d15'), ('d16');
INSERT 0 16
we can then select this data and get an integer row count using the row_number()
window function:
test=> select value, row_number() over (order by id) as rownum from data;
value | rownum
-------+--------
d1 | 1
d2 | 2
d3 | 3
d4 | 4
d5 | 5
d6 | 6
d7 | 7
d8 | 8
d9 | 9
d10 | 10
d11 | 11
d12 | 12
d13 | 13
d14 | 14
d15 | 15
d16 | 16
(16 rows)
Applying the windowed data inside of a subquery, we can select slices of the result based on this count:
test=> select value from (select value, row_number()
> over (order by id) as rownum from data) as sub where rownum between 5 and 10;
value
-------
d5
d6
d7
d8
d9
d10
(6 rows)
So if you had the record "d14" and a page size of 5 you could do this:
test=> select (rownum - 1) / 5 from (select value, row_number()
> over (order by id) as rownum from data) as sub where value='d14';
?column?
----------
2
(1 row)
SQLAlchemy provides window functions via the over() method/function, so a SQLA query assuming a typical ORM mapping for this would look like:
subq = session.query(
Data.value,
func.row_number().over(order_by=Data.id).label('rownum')
).subquery()
pagenum = session.query((subq.c.rownum - 1) / 5).\
filter(subq.c.value == 'd14').scalar()
As far as using window functions vs. limit/offset for pagination, it's worth checking out the comparison I wrote over here comparing the performance of the various methods, as well as the windowed range query recipe I sometimes use.
Upvotes: 2