8

I've a classic pagination system using LIMIT startrecord, endrecord and I want to figure out in what page number an X record is located.

The only idea I've right now is to seek recursively all the records to find it out. But I'm looking for a much more "economic" method!

Any ideas ?

Alireza Fallah
  • 4,609
  • 3
  • 31
  • 57
Lwyrn
  • 1,821
  • 1
  • 16
  • 27
  • what do you mean by an X record? Do you mean 18th record is on page 2? – Waygood Oct 29 '13 at 10:53
  • I mean the record having ID 18 ( or whatever else like having title "Hello" ) is on page 2. And the records can be sorted in a different ways. So I can't just count it – Lwyrn Oct 29 '13 at 11:04
  • Record is recordnumber/perPage – Mihai Oct 29 '13 at 11:05
  • 1
    You could just re run the query again with the added filter, to narrow you results, but apart from that you will have to search the entire results (not just the LIMITed one) – Waygood Oct 29 '13 at 11:07
  • you could always use javascript pagination, which you can sort and filter results. http://datatables.net/ – Waygood Oct 29 '13 at 11:10
  • 1
    @Waygood Javascript pagination is not a good choice. If I've 10k records I've to load all 10k and research in a lot of data! And using a php/js pagination i've to seek in all pages. This is what i want to avoid – Lwyrn Oct 29 '13 at 11:27
  • That's why I suggested running the query again, adding the filter for "id=18" or "title='hello'" to give a subset. I personally don't like the javascript solution, but it may have been suitable for your implementation. – Waygood Oct 29 '13 at 11:53
  • I've to find the page where the id 18 is stored, to show the found pages. Like, if record is on page 4 go to http:/...page4 – Lwyrn Oct 29 '13 at 12:27
  • Will my answer below not provide that. From the outer query, `$page = floor(rank/entriesPerPage)` would tell you which page an entry would be displayed on. You can the redirect to http://...$page – Simon Oct 30 '13 at 09:17
  • Do note that MySQL's `LIMIT` syntax is not `LIMIT startrecord, endrecord`, but `LIMIT offset, count`. That is to page by 10, you don't use `LIMIT 0, 10`, `LIMIT 10, 20`, `LIMIT 20, 30` etc. you use `LIMIT 0, 10`, `LIMIT 10, 10`, `LIMIT 20, 10`. – jeremycole Jan 23 '14 at 21:44
  • Could you take a look at my answer? Please leave some feedback! – Carlos Robles Jan 26 '14 at 05:20

5 Answers5

5

You could use a sub query to create a table with the results and their position, then query that for the specific entry you are looking at:

SET @rank=0; 
SELECT rank, record 
FROM (
    SELECT 
        @rank:=@rank+1 AS rank, 
        record 
    FROM table
) as subquery 
WHERE record = x;

The returned table would show the record an the rank it appeared in the original query. You can the divide the rank by the number of results per page... Or build it into the query. Hope this helps.

Simon
  • 143
  • 10
  • This method seems to be what I need! But if I use a a join in the subquery the result lose the ordering. For example: SET @rank=0; SELECT * FROM ( SELECT pt.id, pt.attr_code, pal.attribute_label, pt.is_required, pt.is_locked, pt.is_visible, pt.is_comparable, @rank:=@rank+1 AS rank FROM fm_product_attributes AS pt INNER JOIN fm_product_attributes_data AS pal ON ( pt.id = pal.attributeid and pal.languageid = 1 ) GROUP BY id ORDER by pt.attr_code ASC ) sq; Using this query the order statement get ignored and the records get default ordination – Lwyrn Oct 30 '13 at 17:38
  • No, the results get sorted as i wish, but the rank variable don't follow it http://s11.postimg.org/bdk2cksr7/rankjoin.png – Lwyrn Oct 30 '13 at 17:49
  • 1
    Is it necessary for you to group by id on the inner query? – Simon Nov 05 '13 at 10:05
4

Cout the number of records that are prior to the one you are looking for. This requires you to assume an order for your query which is natural.

SELECT COUNT(id) AS c
FROM tbl
WHERE sort_field < ((SELECT sort_field FROM tbl WHERE id = 18))
OR (sort_field = ((SELECT sort_field FROM tbl WHERE id = 18)) AND id < 18);

Then just retrieve the c and calculate ceilling(c/page_size). This will give you the page number that your record will fall in. The only important thing to remember is that you need to sort the records in the same order as you would in your query with limit.

To describe what the query does, it counts the number records that stand before the record with id 18. The only tricky part is with records with the same value as for their sort_field in which MySQL will make use of primary key and in our case the id. And that's why we have the OR part in our condition. In my answer I'm assuming you are sorting your original query (with limit statement in it) ascending, but if you are sorting descending then you need to change all of < to >.

Mehran
  • 15,593
  • 27
  • 122
  • 221
3

Use something like this with your query as part of the s subselect

SELECT s.row, s.RECORD, YOUR_OTHER_FIELDS...
FROM (SELECT @row := 0) cnt
  JOIN (SELECT @row := @row + 1 row, RECORD, ...YOUR QUERY WITH ORDER BY ...) s
WHERE s.RECORD = <desired record number>

and divide row by the pagesize from your pagination. Concrete but nonsensical example:

SELECT p.row, p.id
FROM (SELECT @row := 0) cnt
  JOIN (SELECT @row := @row + 1 row, id FROM products ORDER BY id desc) p
WHERE p.id = 485166

As intended, the value of row changes with the order you use in the subselect.

It folds the variable initialization into the query so this is only one statement. It also does not depend on a natural order or distribution of rows - as long as the order they ARE returned in stays the same for whatever ORDER you specify (or leave out).

Arne
  • 1,884
  • 1
  • 15
  • 19
0

if this is something that you will use often, i think it is a good idea to create an stored procedure or a function. We can use a cursor inside, to iterate through the results and get the position of the desired item. I think this will be faster, it wont have to iterate to all the records, and dont need a subquery (for all this i would say that it is more economic) and you can use order, join, and whatever you need.

DELIMITER $$


CREATE FUNCTION position ( looking_for INT ) 
 RETURNS INT
READS SQL DATA
BEGIN
  -- First we declare all the variables we will need
  DECLARE id   INT;
  DECLARE pos   INT;
  SET pos=0;
  -- flag which will be set to true, when cursor reaches end of table
  DECLARE exit_loop BOOLEAN;         

  -- Declare the sql for the cursor
  DECLARE pos_cursor CURSOR FOR
    SELECT id  
    FROM your_table
    --you can use where, join, group by, order and whatever you need 
 --end of query

  -- Let mysql set exit_loop to true, if there are no more rows to iterate
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET exit_loop = TRUE;

  -- open the cursor
  OPEN example_cursor;

  -- marks the beginning of the loop
  example_loop: LOOP

    -- read the id from next row into the variable id
    FETCH  pos_cursor INTO  id;
    -- increment the pos var
    SET pos=pos+1;

    -- check if we found the desired item, 
    -- if it has been set we close the cursor and exit 
    -- the loop
    IF id=looking_for THEN
        CLOSE example_cursor;
        LEAVE example_loop;
    END IF;


    -- check if the exit_loop flag has been set by mysql, 
    -- if it has been set we close the cursor and exit 
    -- the loop
    IF exit_loop THEN
        CLOSE example_cursor;
        LEAVE example_loop;
    END IF;

  END LOOP example_loop;
 RETURN pos;
END $$

DELIMITER ;

You create the function just once, and for using it, you just need to use this sql:

CALL position(ID_OF_THE_ITEM_YOU_ARE_LOOKING_FOR);

and it returns the position of the item, in the position [0][0] of the returned rowset.

Of course instead of the id you can create a function that compares the name, or any other field, or even more than one.

If the query is always diferent, then you cannot use a function, but you can still use the cursor (the syntax will be the same). You can build the cursor in your PHP, let pos be a System variable (using @pos), and in any case just add the specific sql of the query (the part between DECLARE pos_cursor CURSOR FOR and --end of query)

Carlos Robles
  • 10,828
  • 3
  • 41
  • 60
-1

You can't really create an "economic" way. You have to get the full list of records from the DB since there is no way to know the position of a record from MySQL.

Depending on your sorting, the frequency at which the data changes, you could assign the record its position in a column: add column position to the table you are querying. That might not be feasible in all cases.

Fabien Warniez
  • 2,731
  • 1
  • 21
  • 30