Learning Objective
The objective of this SQL Server tutorial is to teach you how to use the OFFSET FETCH clause combination with the ORDER BY clause for pagination.
What is Pagination?
A table might consist of thousands or even millions of tuples. It is not possible to access (by way of displaying and viewing) so much information or even a part of it at the same time in one screen or page. The solution to the problem is pagination. Pagination is the process of breaking up a large volume or chunk of information into pages so that they can be accessed and processed one page at a time.
What is OFFSET FETCH clause combination in SQL Server?
The OFFSET FETCH clause combination can only and only be used in a SELECT statement with the ORDER BY clause. It cannot be used otherwise.
Operation
The OFFSET clause is the mandatory clause in the combination and follows the ORDER BY clause. The FETCH clause is optional and follows the OFFSET clause. Both the clauses take the number of rows as an argument. The number of rows specified with the OFFSET clause indicates the number of rows that must be skipped in the resultset or the number of rows after which the resultset should begin. The number of rows specified with FETCH indicates the number of rows that must be returned or displayed in the resultset.
SQL Server OFFSET FETCH Syntax
The basic syntax of SQL Server OFFSET FETCH clause combination is as follows.
SELECT expressions
FROM tables
[ORDER BY expressions ASC|DESC]
[OFFSET offset_row_count ROW|ROWS]
[FETCH FIRST|NEXT fetch_row_count ROW|ROWS ONLY]
In this syntax,
- expressions – expressions defined here the column(s) or calculation you want to retrieve. If you want to retrieve all the columns simply use * in the place of expressions.
- tables – one or more than one table from where you want to retrieve data.
- ORDER BY – clause used to sort the values in a column in either ascending or descending order.
- ASC|DESC – keyword used to specify how the values of the column specified by ORDER BY should be sorted. Default is ASC (i.e. ascending) and hence does not need to be explicitly specified.
- OFFSET – clause used to specify the number of rows that must be skipped in the resultset or the number of rows after which the resultset should begin. Offset literally means to ignore or cancel out.
- offset_row_count – a non-negative integer value specifying the number of rows to be skipped in the resultset. It can be specified directly or as a variable or as an expression (usually a subquery) which returns a non-negative integer value.
- ROW|ROWS – both ROW and ROWS are synonyms (i.e. mean the same thing). It simply means that the value preceding it is the number of rows. Both the keywords are permissible and supported for compatibility and interoperability reasons.
- FETCH – clause used to specify the number of rows that must be returned or displayed in the resultset.
- FIRST|NEXT – both FIRST and NEXT are synonyms (i.e. mean the same thing). It basically means the first set of rows or next set of rows after the offset. Both the keywords are permissible and supported for compatibility and interoperability reasons.
- fetch_row_count – a non-negative integer value specifying the number of rows to be returned or displayed in the resultset. It can be specified directly or as a variable or as an expression (usually a subquery) which returns a non-negative integer value.
- ONLY – mandatory keyword with FETCH which indicates that only the number of rows specified in the fetch_row_count should be returned in the resultset.
SQL Server OFFSET FETCH Examples
NOTE:
- Columns are also referred to as fields or attributes and the terms are used interchangeably.
- A row of information in a table is called a tuple.
We will see the different usage scenarios for the OFFSET FETCH clause combination using an example table. Let us consider the case of a music store which has a table called music_genre that stores information about the stock available of the different music genres.
genreid | genre | stock |
1 | Pop | 555 |
2 | Rock | 496 |
3 | Rythm & Blues | 125 |
4 | Rap | 317 |
5 | Country | 205 |
6 | Gospel | 111 |
7 | Hip-Hop | 153 |
8 | Jazz | 274 |
9 | Movie Soundtrack | 399 |
10 | Heavy Metal | 515 |
11 | Grunge | 102 |
12 | Ballads | 162 |
1) SQL Server OFFSET FETCH – OFFSET without FETCH example
As it has been mentioned it is possible to use OFFSET without FETCH in which case the query will return all rows in the resultset after skipping the number of rows specified as offset. To understand the same first we run a simple select query which orders the output by stock available.
SELECT *
FROM music_genres
ORDER BY stock;
It produces the below output. Note the sorting in ascending order on the stock column. We will use this resultset as a reference to understand the output of the following examples.
genreid | genre | stock |
11 | Grunge | 102 |
6 | Gospel | 111 |
3 | Rythm & Blues | 125 |
7 | Hip-Hop | 153 |
12 | Ballads | 162 |
5 | Country | 205 |
8 | Jazz | 274 |
4 | Rap | 317 |
9 | Movie Soundtrack | 399 |
2 | Rock | 496 |
10 | Heavy Metal | 515 |
1 | Pop | 555 |
Now we will apply offset in the query to skip 5 rows.
SELECT *
FROM music_genres
ORDER BY stock
OFFSET 5 ROWS;
The query will generate the following output. Note that the first 5 rows from the previous resultset has been removed.
genreid | genre | stock |
5 | Country | 205 |
8 | Jazz | 274 |
4 | Rap | 317 |
9 | Movie Soundtrack | 399 |
2 | Rock | 496 |
10 | Heavy Metal | 515 |
1 | Pop | 555 |
2) SQL Server OFFSET FETCH – OFFSET with FETCH example
We can modify the above query by adding the FETCH clause as below with an argument 5 to indicate that only the next 5 rows after the first 5 rows in the resultset should be returned.
SELECT *
FROM music_genres
ORDER BY stock
OFFSET 5 ROWS
FETCH NEXT 5 ROWS ONLY;
The result will be the below output which is almost the same as above leaving out the last 2 rows and displaying only 5 rows after the offset in the resultset.
genreid | genre | stock |
5 | Country | 205 |
8 | Jazz | 274 |
4 | Rap | 317 |
9 | Movie Soundtrack | 399 |
2 | Rock | 496 |
It is also possible to specify the number of rows for both OFFSET and FETCH as a mathematical calculation as below. Here I have used ROW (instead of ROWS) and FIRST (instead of NEXT) to demonstrate their use as discussed above.
SELECT *
FROM music_genres
ORDER BY stock
OFFSET 12-6 ROW
FETCH FIRST 3+2 ROW ONLY;
The above query will generate the below result. It leaves out the first 6 rows (12-6=6) and returns the next 5 rows (3+2=5) from the original ORDER BY resultset above.
genreid | genre | stock |
8 | Jazz | 274 |
4 | Rap | 317 |
9 | Movie Soundtrack | 399 |
2 | Rock | 496 |
10 | Heavy Metal | 515 |
3) SQL Server OFFSET FETCH – row count derived from expression example
The below query is an example where the number the rows to skip (i.e. the offset_row_count) is specified by a subquery with the OFFSET clause.
SELECT *
FROM music_genres
ORDER BY stock
OFFSET (SELECT COUNT(*) FROM music_genres) - 8 ROWS
FETCH NEXT 4 ROWS ONLY;
The query will generate the following output. Here the subquery returns the count of the total number of rows in the table i.e. 12. This is deducted from 8 (12-8=4) and the resultset skips the first 4 rows and returns the next 4 rows from the original ORDER BY resultset above.
genreid | genre | stock |
12 | Ballads | 162 |
5 | Country | 205 |
8 | Jazz | 274 |
4 | Rap | 317 |
4) SQL Server OFFSET FETCH – row count specified as variable example
It is also possible to use variables which can be reused instead of specifying a constant for the offset_row_count and the fetch_row_count. The same is done when querying the DB through code because it should be possible to run the code as and when required (by varying the variables).
Below is an example of the same. Here we have first declared 2 variables. The first one @startrow indicates the start of the resultset and @rowsperpage indicates the number of rows that will be displayed in each page. The variable declaration syntax is that the variable name will be preceded by @ and followed by datatype and then an optional initial value. Here the datatype is INT and the initial value is 6 and 5 respectively. The query that follows using the variables is the same as the above queries. Only difference is that the offset_row_count is @StartRow – 1 (i.e. 6-1=5) and the fetch_row_count is @RowsPerPage (i.e. 5).
DECLARE @StartRow INT=6;
DECLARE @RowsPerPage INT=5;
SELECT *
FROM music_genre
ORDER BY stock
OFFSET @StartRow - 1 ROWS
FETCH NEXT @RowsPerPage ROWS ONLY;
It produces the below result leaving out the first 5 rows and returning the next 5 rows from the original ORDER BY resultset.
genreid | genre | stock |
5 | Country | 205 |
8 | Jazz | 274 |
4 | Rap | 317 |
9 | Movie Soundtrack | 399 |
2 | Rock | 496 |