There are a method to paging using ANSI SQL only?
Asked Answered
B

9

13

I know:

  • Firebird: FIRST and SKIP;
  • MySQL: LIMIT;
  • SQL Server: ROW_NUMBER();

Does someone knows a SQL ANSI way to perform result paging?

Bead answered 21/1, 2009 at 1:42 Comment(2)
see that: #6598676Mathews
For SQL Server, it's better to use ORDER BY <order_by_expr> OFFSET <skip> FETCH NEXT <count> ROWS instead of the ROW_NUMBER() windowing function. Support for OFFSET/FETCH was added in SQL Server 2012 (which postdates this question).Nosegay
S
8

See Limit—with offset section on this page: http://troels.arvin.dk/db/rdbms/

BTW, Firebird also supports ROWS clause since version 2.0

Starwort answered 21/1, 2009 at 14:58 Comment(0)
T
3

No official way, no.*

Generally you'll want to have an abstracted-out function in your database access layer that will cope with it for you; give it a hint that you're on MySQL or PostgreSQL and it can add a 'LIMIT' clause to your query, or rownum over a subquery for Oracle and so on. If it doesn't know it can do any of those, fall back to fetching the lot and returning only a slice of the full list.

*: eta: there is now, in ANSI SQL:2003. But it's not globally supported, it often performs badly, and it's a bit of a pain because you have to move/copy your ORDER into a new place in the statement, which makes it harder to wrap automatically:

SELECT * FROM (
    SELECT thiscol, thatcol, ROW_NUMBER() OVER (ORDER BY mtime DESC, id) AS rownumber
)
WHERE rownumber BETWEEN 10 AND 20 -- care, 1-based index
ORDER BY rownumber;

There is also the "FETCH FIRST n ROWS ONLY" suffix in SQL:2008 (and DB2, where it originated). But like the TOP prefix in SQL Server, and the similar syntax in Informix, you can't specify a start point, so you still have to fetch and throw away some rows.

Typo answered 21/1, 2009 at 2:26 Comment(1)
The ISO SQL:2008 standard specifies that you can indicate a starting point using "OFFSET n" before "FETCH FIRST m ROWS ONLY". But I know of no product which currently implements OFFSET.Confer
W
3

In nowadays there is a standard, not necessarily a ANSI standard (people gave many anwsers, I think this is the less verbose one)

SELECT * FROM t1 
WHERE ID > :lastId
ORDER BY ID
FETCH FIRST 3 ROWS ONLY

It's not supported by all databases though, bellow a list of all databases that have support

  • MariaDB: Supported since 5.1 (usually, limit/offset is used)
  • MySQL: Supported since 3.19.3 (usually, limit/offset is used)
  • PostgreSQL: Supported since PostgreSQL 8.4 (usually, limit/offset is used)
  • SQLite: Supported since version 2.1.0
  • Db2 LUW: Supported since version 7
  • Oracle: Supported since version 12c (uses subselects with the row_num function)
  • Microsoft SQL Server: Supported since 2012 (traditionally, top-N is used)

You can use the offset style of course, although you could have performance issues

SELECT * FROM t1
ORDER BY ID
OFFSET 0 ROWS
FETCH FIRST 3 ROWS ONLY

It has a different support

  • MariaDB: Supported since 5.1
  • MySQL: Supported since 4.0.6
  • PostgreSQL: Supported since PostgreSQL 6.5
  • SQLite: Supported since version 2.1.0
  • Db2 LUW: Supported since version 11.1
  • Oracle: Supported since version 12c
  • Microsoft SQL Server: Supported since 2012
Waterish answered 11/9, 2018 at 16:8 Comment(2)
Do you have a source for the supported versions for these? I've tried both MySQL 5.5 and MariaDB 10.0 and neither supports FETCH FIRST 3 ROWS ONLY by default (is a setting required?). The most universal method seems to just be the ID > :lastID part, then fetch only as many rows as you need, hoping any excess fetched by the DBMS isn't too wasteful.Maxim
@haravikk yep, I found it on the book Mastering Postgres, you can see at this linkWaterish
E
3

Yes (SQL ANSI 2003), feature E121-10, combined with the F861 feature you have :

ORDER BY column OFFSET n1 ROWS FETCH NEXT n2 ROWS ONLY;

Like:

SELECT Name, Address FROM Employees ORDER BY Salary OFFSET 2 ROWS
FETCH NEXT 2 ROWS ONLY;

Examples:

Unfortunately, MySQL does not support this syntax, you need something like:

ORDER BY column LIMIT n1 OFFSET n2
Elmaleh answered 20/3, 2022 at 16:3 Comment(0)
M
1

I know I'm very, very late to this question, but it's still one of the top results for this issue.

However one response missing for this question is that the I believe the "correct" ANSI SQL method for paging, at least if you want maximum portability, is to not to use LIMIT/OFFSET/FIRST etc. at all, but to instead do something like:

SELECT *
FROM MyTable
WHERE ColumnA > ?
ORDER BY ColumnA ASC

Where ? is a parameter using a library that supports them (such as PDO in PHP).

The idea here is simple, when fetching the first page we pass a parameter that will match every possible row, e.g- if ColumnA is text, we would pass an empty string (''). We then read in as many results as we want, and then release the rest. This may mean some extra rows are fetched behind the scenes, but our priority here is compatibility.

In order to fetch the next page, we take the value of ColumnA from the last row in our results, and pass it in as the parameter, this way we will only fetch values that appear after it. To run the same query in the other direction, just swap > for < and ASC for DESC.

There are some important caveats of this approach:

  1. Since we're using a condition, your DBMS is free to use an index to optimise the request, which can actually be faster than some "proper" pagination methods, as you eliminate rows rather than advancing past them.
  2. This form of paging is more tightly anchored than row number based methods. When using row number offsets, if you offset into the table, but new rows are added that sort earlier than the current page, then it will cause results to be shifted into later pages. For example, if your current page's last row is mango but since fetching it rows are added for apple and carrot, then mango may now appear on the next page as well, as it has been shifted in the sort order. By using a condition of ColumnA > 'mango' this can't happen. This can be very useful in cases where you are sorting by a DATETIME with frequent updates occurring.
  3. This trick can be made to work in both directions, by reversing the sort order as mentioned when going backwards (flip > to < and ASC to DESC) and passing in the value of ColumnA from the first row of each page of results, rather than the last. Note that if values were added to your table, it may mean that your first page may be shorter, but this is a fairly minor issue.
  4. To be sure you're on the last (or first) page, you should fetch N + 1 rows, where N is the number of rows you want per page, this way you can detect whether there are more rows to fetch.
  5. This method works best if you have a single column with only unique values, but it is still possible to use in more complex cases, so long as you can expand your ORDER BY clause (and WHERE condition) to include enough columns that every row is unique.

So it's not without a few catches, but it's by far the most compatible method as every SQL database will support it.

Maxim answered 23/10, 2019 at 17:24 Comment(0)
P
0

Insert your results into a storage table, ordered how you'd like to display them, but with a new IDENTITY column.

Now SELECT from that table just the range of IDs you're interested in.

(Be sure to clean out the table when you're done)


Or do it on the client, as anything to do with presentation should not normally be done on the SQL Server (in my opinion)

Phonetic answered 21/1, 2009 at 1:51 Comment(0)
V
0
ANSI Sql example:
offset=41, fetchsize=10

SELECT TOP(10) *
FROM table1
WHERE table1.ID NOT IN (SELECT TOP(40) table1.ID FROM table1)
Vehemence answered 3/6, 2010 at 12:35 Comment(1)
nice! but I think that TOP isn't sql ansi, is T-SQL :(Bead
I
0

For paging we need a RowNo column to filter over it -that it should be over a field like id- with two variables like @PageNo and @PageRows. So I use this query:

SELECT *
FROM (
    SELECT *, (SELECT COUNT(1)
               FROM aTable ti
               WHERE ti.id < t.id) As RowNo
    FROM aTable t) tr
WHERE
    tr.RowNo >= (@PageNo - 1) * @PageRows + 1 
 AND
    tr.RowNo <= @PageNo * @PageRows 
Interim answered 14/5, 2015 at 11:41 Comment(0)
O
-1

BTW, Troels, PostgreSQL supports Limit/Offset

Ossetia answered 29/8, 2009 at 1:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.