views:

127

answers:

4

Mysql: i need to get the offset of a item in a query.

I have a image gallery: this show 6 image per stack, so when i request image 22 it shows images from 18 to 24. It should first get the offset of the image 22, then get the images from 18 to 24.

Another example: i request the image number 62(and offset 62), it will select images with offset from 60 to 66.

Is possible with a single query?

The main important thing is to get the offset value of the item that has its id equal to a number.

Thanks ;)

EDIT: select * from images order_by updated_at offset(here i need to get the offset of the image id in this query, and the make some calculation... this is what i need, if is possible.. :d)

EDIT2: Now I understand that I need 2 queries:

1º: get the offset of the image within the query with my custom order

2º: get the images using the offset from the first query... this I can make it alone, the first one is the problem.. :s

+3  A: 

If your images have sequential IDs, you may want to do the following:

SELECT    * 
FROM      images 
WHERE     id >= ((? DIV 6) * 6) AND 
          id < (((? DIV 6) + 1) * 6)
ORDER BY  id;

Replace the ? parameter in the above query with the ID of the image requested.


UPDATE: It seems that your images are not ordered by a sequential ID, but by a timestamp. Unfortunately it looks like MySQL does not support variable expressions in the LIMIT clause (Source). One option would be to use a prepared statement:

PREPARE stmt FROM 
" SELECT    * 
  FROM      images
  ORDER BY  updated_at
  LIMIT     ?, 6";

SET @lower_limit := ((22 DIV 6) * 6);

EXECUTE stmt USING @lower_limit;

Another option could be:

SET @row = 0;

SELECT    * 
FROM      images 
WHERE     (@row := @row + 1) BETWEEN ((3 DIV 6) * 6) + 1 and (((3 DIV 6) + 1) * 6)
ORDER BY  updated_at;
Daniel Vassallo
but is not ordered by id :s, is ordered by a timestamp, updated_at
Totty
The expressions you have with the remainder operator do not give the correct results. ((62 % 6) - 1) * 6 = 6. What you want is the quotient of integer division. With the remainder you will never get any results above 24, and you can get -6 for inputs that are less than 6.
Martinho Fernandes
@Martinho: You're right, Thanks for noting... I'm fixing it.... Fixed.
Daniel Vassallo
you are only using the id, but what i really need is the offset of the image in a query with a order_by = "updated_at". So, if i request the image id=22, with ordered by "updated_at" will get a offset of 4, for example, not 22.
Totty
@user305270: you did not make that clear in your question, because in all your examples the id and offset were the same thing.
Martinho Fernandes
@Daniel: still not right... (22 DIV 6 - 1) * 6 - 1 = 11... It's as simple as (22 DIV 6) * 6.
Martinho Fernandes
@user305270: The last two options should work for pagination without a sequential ID. Note that they are being ordered by `updated_at`.
Daniel Vassallo
@martino yes, now i corrected, im sorry
Totty
i dont understand this and what it means: (@row := @row + 1)but is not working, there is no rows selected. 0
Totty
@Martinho: Yes, had a mistake there. Fixed now. `LIMIT` starts from 0, so I still need to subtract 1 in order to list the items from 18 to 23, when ID=22 is given.
Daniel Vassallo
@user305270: Make sure you call the SET `@row := 0;` before. That is just a trick that identifies the row ID. The query is simply returning rows from 18 to 23. However note that the prepared statement method is better, for performance and security.
Daniel Vassallo
@Daniel: If `LIMIT` starts from 0, you don't need to subtract 1. (22 DIV 6)*6 = 18. What happens when the input is 3? (3 DIV 6)*6 = 0. If you subtract 1 you get -1. The whole reason of using 0 as the base is to avoid these kinds of -1 adjustments.
Martinho Fernandes
i got an error if i use numbers inferiors to 6
Totty
i have 7 rows in my query, so when i should use 7 instead of 22 i need to get only 1 row, but i get 2, and none of these is right :s
Totty
@Martinho: You're right... I'm not thinking today :)
Daniel Vassallo
@user305270: Should be fixed now.
Daniel Vassallo
Thanks ;) it really helped a lot.. now i have to see how may i use this with ruby on rails.I googled the trick with the SET @row, but i didnt find anything, do you have a link to some page that explains this? thanks ;)
Totty
the code now is perfect ;) no bugs found :D
Totty
@user305270: You may want to check: http://forums.mysql.com/read.php?98,28212,260408#msg-260408, where they explain how to use the `@row` method to workaround this problem. Some DBMSes support a `ROWID()` function that returns the row number of a result set, which could be used to apply further filtering. MySQL does not feature this function, and that is just a workaround for this... Note however, that I would still prefer the prepared statement method, especially if you will be dealing with a large number of rows.
Daniel Vassallo
so the prepared is better in terms of performance?(you know how may i use it on a rails application? thanks)
Totty
@user305270: Yes, apart from performance, they are considered a best practice solution to avoid SQL injection... No experience with rails unfortunately. You may want to search on Stack Overflow or ask another question. Prepared Statements are very common.
Daniel Vassallo
+1  A: 

EDIT: It appears MySQL does not allow this kind of expression in the OFFSET clause. If you can use a prepared statement (either directly in SQL or in another language), you can make the calculation beforehand and use it. See Daniel's answer for that. I'm leaving this answer here because of the other useful information.

What you are looking for is called pagination. In MySQL you can do it with the LIMIT and OFFSET keywords:

SELECT   image
FROM     images
ORDER BY updated_at
LIMIT    6
OFFSET   (DIV(?, 6) * 6)

Replace ? with the requested image index. Note that this will give images with offsets 60, 61, 62, 63, 64, 65 when you ask for image 62. I assumed the last offset you gave in the examples was exclusive. You should adjust accordingly if I made the wrong assumption.

And a little explanation:

LIMIT makes the query return only the given number of results. Because you always want six, that makes it easy.

OFFSET makes the query return only results from the given offset. The calculation does integer division by six and multiplies by six. This results in the previous multiple of six of the given number, exactly what you want.

Martinho Fernandes
MySQL (at least version 5.0.51a) does not support that kind of expression in the `OFFSET` clause... I'm not sure about later versions.
Daniel Vassallo
@Daniel: That's bad. I guess the poster will need to use your solution with a variable, then.
Martinho Fernandes
@Martinho: Yes it's a pity `LIMIT` and `OFFSET` arguments must be integer constants... The bug/feature request is listed here: http://bugs.mysql.com/bug.php?id=11918. It doesn't look like it has been fixed in version 5.1.
Daniel Vassallo
its not working too. But how I get the "?"? the ? is not the images' id, is the offset of that image in a query.
Totty
A: 
SELECT   image
FROM     images
ORDER BY updated_at
LIMIT    6, FLOOR($number / 6 ) * 6;
bigstylee
MySQL only supports integer constants in the `LIMIT` clause: http://bugs.mysql.com/bug.php?id=11918.
Daniel Vassallo
+1  A: 

I may not understand you, but why do you have to do everything in MySQL?

Let's assume you use LAMP:

$pagination_start = (int)(floor($id_requested / 6)*6);
$offset_array = ($id_requested % 6);
$offset_mysql = $pagination_start + $offset_array;

Now you have start of your pagination in $pagination_start [i.e. 60] and requested image offset [i.e. 62] in $offset_mysql:

SELECT   image
FROM     images
ORDER BY updated_at DESC
LIMIT    $pagination_start, 6

Now in return, you get an array cotaning 6 images and the one requested is at $result[$offset_array].

Adam Kiss
The OP did not make this clear before, but when the request is for the image with id 22, that image can be say, the 62nd most recently updated one. What we want then is for the query to return the 60th to 66th (with, I assume, an exclusive upper bound) most recently updated ones. At least that's what I got from his comments.
Martinho Fernandes
Ah. Now I deleted my and? answer and will think about it again :D
Adam Kiss
I still think he doesn't request actuall `id`, but more like `number at position XY in this gallery`—in that case, this works as promised :)
Adam Kiss
you dont work on the offset position in a query, but just on the id of a row.
Totty