5

I'm trying to set up a pagination using SQL. I want 3 results per page and here is what I have done :

SELECT mot_cle.* FROM mot_cle 
ORDER BY hits DESC LIMIT 3 OFFSET 0; --Page 1

SELECT mot_cle.*  FROM mot_cle 
ORDER BY hits DESC LIMIT 3 OFFSET 3; --Page 2

SELECT mot_cle.*  FROM mot_cle 
ORDER BY hits DESC LIMIT 3 OFFSET 6; --Page 3

SELECT mot_cle.* 
FROM mot_cle 
ORDER BY hits DESC LIMIT 3 OFFSET 9; --Page 4

I checked many times and this is not very complicated but my results are not really what I expected :

Page 1 :

+-----+--------+------+
| id  |  mot   | hits |
+-----+--------+------+
|   2 | test   |   46 |
|   1 | blabla |    5 |
| 475 | intro  |    3 |
+-----+--------+------+

Page 2 :

+-----+-------+------+
| id  |  mot  | hits |
+-----+-------+------+
| 478 | vrai  |    1 |
|  26 | ouest |    1 |
|  27 | serie |    1 |
+-----+-------+------+

Page 3 :

+-----+-------+------+
| id  |  mot  | hits |
+-----+-------+------+
|  27 | serie |    1 |
|  26 | ouest |    1 |
| 478 | vrai  |    1 |
+-----+-------+------+

Page 4 :

+-----+-------+------+
| id  |  mot  | hits |
+-----+-------+------+
|  27 | serie |    1 |
|  26 | ouest |    1 |
| 478 | vrai  |    1 |
+-----+-------+------+

As you can see, pages 2, 3 and 4 have the same results... When I fetch the 4 pages in one :

SELECT mot_cle.*  FROM mot_cle 
ORDER BY hits DESC LIMIT 20 OFFSET 0;

Result :

+-----+-------------+------+
| id  |     mot     | hits |
+-----+-------------+------+
|   2 | test        |   46 |
|   1 | blabla      |    5 |
| 475 | intro       |    3 |
|  35 | acteurs     |    1 |
|  36 | milieu      |    1 |
|  37 | industriel  |    1 |
|  38 | plaire      |    1 |
|  39 | grandes     |    1 |
|  40 | ingenieries |    1 |
|  41 | francaises  |    1 |
|  34 | partenaire  |    1 |
|  33 | rthgyjhkj   |    1 |
|  32 | cool        |    1 |
|  31 | super       |    1 |
|  30 | vieux       |    1 |
|  29 | moteur      |    1 |
|  28 | yahoo       |    1 |
|  27 | serie       |    1 |
|  26 | ouest       |    1 |
| 478 | vrai        |    1 |
+-----+-------------+------+

Maybe I'm missing something or sorting results and using limit/offset are not compatible, I don't know what's wrong.

Martin Tournoij
  • 26,737
  • 24
  • 105
  • 146
groseb
  • 59
  • 1
  • 6
  • 1
    The behavior you're observing is within spec. What you're missing is a second expression in the `ORDER BY` clause, to make the order deterministic. The rows *are* being "ordered by" `hits`, but there's multiple rows with an *identical* value for `hits`. MySQL is free to return those rows with matching value of `hits` in any order it wants. To get those rows returned in a deterministic order, add some expressions to the ORDER BY clause, some combination of expressions that are unique for a row. It looks like the `id` column may be unique. E.g. **`... ORDER BY hits DESC, id DESC LIMIT ...`** – spencer7593 Jan 06 '15 at 17:10
  • Does this answer your question? [Strange ordering bug (is it a bug?) in postgres when ordering two columns with identical values](https://stackoverflow.com/questions/11904766/strange-ordering-bug-is-it-a-bug-in-postgres-when-ordering-two-columns-with-i) – philipxy Oct 05 '20 at 00:13

3 Answers3

16

The problem here is that the rows all have a hit count of 1, therefore their position when using ORDER BY hits is non-deterministic. And since you execute a new query each time you access a page, the rows will be "scrambled" anew.

To keep your pages consistent, you could also order by their id:

SELECT mot_cle.* FROM mot_cle ORDER BY hits DESC, id ASC LIMIT 3 OFFSET 0; --Page 1
Timo D
  • 1,723
  • 10
  • 16
2

LIMIT basically "aborts" the query once enough matching rows have been found. E.g. if you have LIMIT 2, then as soon as two rows which match the join/where clauses are found, the rest of the query goes away and you get those two rows.

But if you have an ORDER BY in there, then the ENTIRE matching result set is ordered, then the LIMIT is applied to that sorted set.

e.g. if you have some records that'd be returned as 5,10,203,3,92, then

SELECT id ... LIMIT 2             ->    (5,10),203,3,92
                                        ^^^^^^---actual returned-to-client results
SELECT id ... ORDER BY id LIMIT 2 ->    3,5,10,92,203 -> (3,5),10,92,203
                                           ^^---internal-only results
                                                         ^^^^--actual returned-to-client results.
Marc B
  • 356,200
  • 43
  • 426
  • 500
-1

Instead of using

Limit 3 offset 0
Limit 3 offset 3
Limit 3 offset 6
Limit 3 offset 9

you should be able to use

Limit 0,3
Limit 3,3
Limit 6,3
Limit 9,3

to the same effect

  • 1
    This is not really an answer to the question asked. – Mike G Jan 06 '15 at 16:46
  • Yes, this is alternative syntax for the `LIMIT` clause, but this doesn't explain the behavior being observed by OP. And making this change won't have any affect on the rows being returned. – spencer7593 Jan 06 '15 at 17:12