{"id":96331,"date":"2023-03-20T21:37:35","date_gmt":"2023-03-20T21:37:35","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=96331"},"modified":"2023-06-06T19:20:24","modified_gmt":"2023-06-06T19:20:24","slug":"paging-data-in-t-sql","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/paging-data-in-t-sql\/","title":{"rendered":"Paging Data in T-SQL"},"content":{"rendered":"<p style=\"margin: 0in; font-family: Calibri; font-size: 11.0pt;\"><p><strong>This article is part of Greg Larsen's continuing series on Learning T-SQL. To see all the items in the series, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/t-sql-coding-basics\/\">click here<\/a>.<\/strong><\/p>\n<\/p>\n<p>Sometimes when working with very large sets of data, you may not want to return all of the data at once. I discussed using <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/using-top-clause-in-a-select-statement\/\">TOP<\/a> in my previous article, which allowed you to only get a number of rows from the start of the results from a query. However, if you want to see the rows after that top set,<\/p>\n<p>paging of data takes that further to let you scroll through a set of data one page at time. So, you might want to fetch the first 100 rows, then another 100, then the rest of the rows, etc.<\/p>\n<p>This article will show you how to page through a set of results using the <code>OFFSET<\/code> and <code>FETCH<\/code> options of the <code>ORDER BY<\/code> clause.<\/p>\n<h2>Sample Data<\/h2>\n<p>Before showing some different paging options, I will create some test data using the code in Listing 1.<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk \">-- Create test data\r\nUSE tempdb;\r\nGO\r\n\r\nDROP TABLE IF EXISTS TestData; \r\nGO\r\n\r\nCREATE TABLE TestData (\r\nID INT IDENTITY, \r\nCityName VARCHAR(20),\r\nStateName VARCHAR(20),\r\nFounded SMALLINT);\r\n\r\n--Insert rows of test data\r\nINSERT INTO TestData VALUES\r\n('Seattle','Washington',1851),\r\n('Redmond','Washington',1871),\r\n('Bellevue','Washington',1953),\r\n('Spokane','Washington',1881),\r\n('Tacoma','Washington',1872),\r\n('Portland','Oregon',1851),\r\n('Grants Pass','Oregon',1887),\r\n('Salem','Oregon',1842),\r\n('Bend','Oregon',1905);<\/pre>\n<p><strong>Listing 1: Creating Sample Data<\/strong><\/p>\n<p>In Listing 1, a table named <code>TestData<\/code> was created that contains a list of cities. This data will be used in the different paging examples below. If you want to follow along and run the example code in this article you can create the sample <code>TestData<\/code> on your test instance of SQL Server.<\/p>\n<h1>Page through data in T-SQL<\/h1>\n<p>In order to page through a page of data using T-SQL the <code>OFFSET<\/code> and <code>FETCH<\/code> options of the <code>ORDER BY<\/code> clause are used. SQL Server starts returning data from a specific row based on the <code>OFFSET<\/code> value and returns a specific number of rows based on the <code>FETCH<\/code> value.<\/p>\n<p>Using the <code>OFFSET<\/code> and <code>FETCH<\/code> options of the <code>ORDER BY<\/code> clause is a better option for paging then using <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/t-sql\/language-elements\/declare-cursor-transact-sql?view=sql-server-ver16\">a server-side cursor<\/a>.<\/p>\n<h3>Syntax for the OFFSET and FETCH<\/h3>\n<p>Below is the syntax for the <code>ORDER BY<\/code> clause as found in the <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/t-sql\/queries\/select-order-by-clause-transact-sql?view=sql-server-ver16\">Microsoft Documentation<\/a>.<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">ORDER BY order_by_expression \r\n  [ COLLATE collation_name ]  \r\n  [ ASC | DESC ]  \r\n  [ ,...n ]  \r\n[ &lt;offset_fetch&gt; ] \r\n&lt;offset_fetch&gt; ::= \r\n{  \r\n  OFFSET { integer_constant | offset_row_count_expression } \r\n      { ROW | ROWS } \r\n  [ \r\n   FETCH { FIRST | NEXT } {integer_constant | \r\n      fetch_row_count_expression } { ROW | ROWS } ONLY \r\n  ] \r\n}} <\/pre>\n<p>The <em>&lt;offset_fetch&gt; <\/em>option is an optional item that is used in conjunctions with the <code>ORDER BY<\/code> clause to page through a set of data. It has two components:<\/p>\n<p><code>OFFSET<\/code> and <code>FETCH<\/code><em>. <\/em><\/p>\n<p>The <code>OFFSET<\/code> option identifies the number of rows in an ordered row set to skip before rows are returned. The <code>FETCH<\/code> option is optional and identifies the number of rows that will be returned. If the <code>FETCH<\/code> option is not specified all rows from the <code>OFFSET<\/code> location to the end of the ordered set are returned.<\/p>\n<p>To show how the <code>OFFSET<\/code> and <code>FETCH<\/code> clauses let\u2019s go through a few different examples.<\/p>\n<h3>Using the OFFSET option<\/h3>\n<p>The <code>OFFSET<\/code> option of the <code>ORDER BY<\/code> clause is used to identify the number of rows to skip in a record set before rows are returned. The value can be from 0 (zero) or any number up to the number of rows in the set. When zero (0) is used no rows are skipped, as shown when the code in Listing 2 is executed.<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT * \r\nFROM TestData\r\nORDER BY ID \r\nOFFSET 0 ROWS;<\/pre>\n<p><strong>Listing 2: Skipping zero rows.<\/strong><\/p>\n<p>When Listing 2 is executed the output showing in Report 1 is created.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-96333\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/03\/a-picture-containing-text-crossword-puzzle-recei-1.png\" alt=\"A picture containing text, crossword puzzle, receipt\n\nDescription automatically generated\" width=\"297\" height=\"239\" \/><\/p>\n<p><strong>Report 1: Output when Listing 2 is run.<\/strong><\/p>\n<p>As you can see when Listing 2 is run every row in table <code>TestData<\/code> is returned. In Listing 2 no rows were skipped because 0 (zero) was used for the <code>OFFSET<\/code> value and the <code>FETCH<\/code> option is not provided so all rows are returned from the sample data table.<\/p>\n<p>Suppose the first 5 rows based on ID values needed to be skipped when selecting data. To meet that requirement the code in Listing 3 could be executed.<\/p>\n<pre class=\"lang:c# theme:vs2012\">-- Skipping 5 rows\r\nDECLARE @Skip INT = 5;\r\nSELECT * FROM TestData\r\nORDER BY ID\r\n OFFSET @Skip ROWS;<\/pre>\n<p><strong>Listing 3: Skipping 5 rows<\/strong><\/p>\n<p>The code in Listing 3, this time, specified that 5 rows would be skip. by using a variable instead of a constant. When Listing 3 is executed Report 2 is produced.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"316\" height=\"125\" class=\"wp-image-96336\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/03\/word-image-96331-2-1.png\" \/><\/p>\n<p><strong>Report 2: Output created when Listing 3 is run.<\/strong><\/p>\n<p>By reviewing the output, in Report 2, you can see only the records with the <code>ID<\/code> value of greater than 5 and a <code>StateName<\/code> value of \u201cOregon\u201d are displayed this time. That is because the first 5 rows in the <code>TestData<\/code> table based on the <code>ID<\/code> value were skipped before the rest of the test data table rows are returned using the <code>SELECT<\/code> statement.<\/p>\n<p>Each example so far has only shown how to skip rows. If you want to limit the number of rows displayed the <code>FETCH<\/code> option needs to be used.<\/p>\n<h3>Using the FETCH option<\/h3>\n<p>Assume you what to skip no rows in the record set, but only display just the first three rows of data based on the <code>ID<\/code> column value. If this was the requirement, then the code in Listing 4 could be executed.<\/p>\n<pre class=\"lang:c# theme:vs2012\">-- Display first 3 rows\r\nDECLARE @Skip INT = 0;\r\nDECLARE @Fetch INT = 3;\r\n\r\nSELECT * \r\nFROM TestData\r\nORDER BY ID\r\n OFFSET @Skip ROWS\r\n      FETCH NEXT @Fetch ROWS ONLY;<\/pre>\n<p><strong>Listing 4: Displaying the first 3 rows<\/strong><\/p>\n<p>In Listing 4 another variable was declared <em>@<\/em><code>Fetch<\/code>, which identifies the number of rows to return. It was set to the value 3. When the code in listing 4 is executed the output in Report 3 was produced.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-96338\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/03\/table-description-automatically-generated-7.png\" alt=\"Table\n\nDescription automatically generated\" width=\"313\" height=\"116\" \/><\/p>\n<p><strong>Report 3: Output created with Listing 4 is run.<\/strong><\/p>\n<p>By reviewing Report 3 you can see that zero rows were skipped, as identified by the <code>OFFSET<\/code> value. Plus, only the first 3 rows of the <code>TestData<\/code> table based on the <code>ID<\/code> column were displayed, because the <code>FETCH<\/code> option variable @<code>Fetch<\/code> was set to 3.<\/p>\n<p>Suppose you wanted to display the first three cities in Oregon based on the ID column value. To accomplish this requirement the code in Listing 5 could be run.<\/p>\n<pre class=\"lang:c# theme:vs2012\">-- Display first 3 Oregon Cities\r\nDECLARE @Skip INT;\r\nDECLARE @Fetch INT = 3;\r\n\r\nSELECT TOP (1) @SKIP = ID - 1 FROM TestData \r\nWHERE StateName = 'Oregon';\r\n\r\nSELECT * FROM TestData\r\nORDER BY ID\r\n OFFSET @Skip ROWS\r\n      FETCH NEXT @Fetch ROWS ONLY;<\/pre>\n<p><strong>Listing 5: Displaying first three cities in Oregon. <\/strong><\/p>\n<p>When the code in Listing 5 is executed the results in Report 4 is displayed<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"315\" height=\"96\" class=\"wp-image-96340\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/03\/word-image-96331-4-1.png\" \/><\/p>\n<p><strong>Report 4: Results displayed with Listing 5 is run.<\/strong><\/p>\n<p>In Listing 5 the <em>@<\/em><code>Skip<\/code> variable was set programmatically using a <code>SELECT<\/code> statement. That statement identified the <code>ID<\/code> value for the first row that had \u201c<code>Oregon<\/code>\u201d set as the <code>StateName<\/code><em>. <\/em>By programmatically setting the @<em>Skip<\/em> variable, all of the Washington state cities were skipped. Only the first 3 Oregon state rows were displayed because the @<code>Fetch<\/code> variable was set to 3.<\/p>\n<h1>Paging through data with a loop<\/h1>\n<p>The examples in the prior sections showed how to use the <code>OFFSET<\/code> and <code>FETCH<\/code> options to identify the rows to skip and display from the sample data table. By changing the <code>OFFSET<\/code> and <code>FETCH<\/code> values between calls to SQL Server an application can page through a table of data. This is particularly useful when you need to display one page at a time while paging through a table with a large number of rows. By using the <code>OFFSET<\/code> and <code>FETCH<\/code> options of the <code>ORDER BY<\/code> clause will minimize the amount of data transmitted back to the client, by only sending one page data at a time to the application.<\/p>\n<p>To simulate paging through the sample data my example will use a <code>WHILE<\/code> loop. The code in Listing 6 pages through the sample data displaying 3 rows of data at a time. Keep in mind while you review this example a client application would normally perform the operations of looping through data one page at a time.<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">-- Paging through sample data\r\nDECLARE @Skip INT = 0 ;\r\nDECLARE @Fetch INT = 3;\r\nDECLARE @LoopCnt INT;\r\n\r\nSELECT @LoopCnt = COUNT(*) \/ @Fetch \r\nFROM TestData;\r\n\r\nWHILE @LoopCnt &gt; 0\r\nBEGIN\r\n  SET @LoopCnt = @LoopCnt - 1;\r\n\r\n  SELECT * FROM TestData\r\n  ORDER BY ID\r\n  OFFSET @Skip ROWS\r\n  FETCH NEXT @Fetch ROWS ONLY;\r\n\r\n-- Adjust the rows to skip\r\n  SET @SKIP = @SKIP + @Fetch;\r\nEND<\/pre>\n<p><strong>Listing 6: Paging through sample data 3 rows at a time<\/strong><\/p>\n<p>The first time through the <code>WHILE<\/code> loop the rows in Report 5 are displayed. This is because the @<code>Offset<\/code> value is set to zero and the @<code>Fetch<\/code> option is set to 3.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-96343\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/03\/table-description-automatically-generated-9.png\" alt=\"Table\n\nDescription automatically generated\" width=\"302\" height=\"103\" \/><\/p>\n<p><strong>Report 5: First time through the loop.<\/strong><\/p>\n<p>Before the second time through the loop the @<em>Skip <\/em>value is increased by the value of 3 that is contained in the @<em>Fetch <\/em>variable. The rows in Report 5 are displayed for the second time the loop is executed.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"297\" height=\"94\" class=\"wp-image-96344\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/03\/word-image-96331-6-1.png\" \/><\/p>\n<p><strong>Report 6: Second time through loop. <\/strong><\/p>\n<p>Before the last time through the loop the <em>@Skip <\/em>is increased again by 3. Report 7 shows the rows displayed for the third time through the loop.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"312\" height=\"93\" class=\"wp-image-96346\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/03\/word-image-96331-7-1.png\" \/><\/p>\n<p><strong>Report 7: Last time through the loop.<\/strong><\/p>\n<p>As you can see by adjusting the @<code>Skip<\/code> variable between each time through the loop the next set of 3 rows where displayed.<\/p>\n<h1>A caveat: Changes to underlying data<\/h1>\n<p>One of the biggest concerns when paging data is that you are not holding any locks or version control of the results. Each execution of the <code>SELECT<\/code> statement fetching rows is executing the query again. This could be an issue for hard to optimize query, but there is one more interesting issue with that. Changes to the results of your query.<\/p>\n<p>Aany change to the underlying data can cause you to see rows again, or perhaps miss rows. For example, consider the following set of statements in Listing 7:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">-- Display first 3 rows\r\nDECLARE @Skip INT = 0;\r\nDECLARE @Fetch INT = 3;\r\n\r\nSELECT * FROM TestData\r\nORDER BY ID OFFSET @Skip ROWS\r\n      FETCH NEXT @Fetch ROWS ONLY;\r\nGO<\/pre>\n<p><strong>Listing 7: Showing the effect of changing rowsets<\/strong><\/p>\n<p>This returns the output you see in report 8:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-96348\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/03\/table-description-automatically-generated-11.png\" alt=\"Table\n\nDescription automatically generated\" width=\"339\" height=\"116\" \/><\/p>\n<p><strong>Report 8: Result from fetching first three rows<\/strong><\/p>\n<p>Next, in Listing 8, I will delete the row with ID = 3, and then run the statement that an application would execute if paging through these rows. It is the same code as in Listing 7, but I skipped 3 rows instead of 0.<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">DELETE FROM TestData\r\nWHERE ID = 3;\r\nGO\r\n\r\n-- Display first 3 rows\r\nDECLARE @Skip INT = 3;\r\nDECLARE @Fetch INT = 3;\r\n\r\nSELECT * F\r\nROM TestData\r\nORDER BY ID OFFSET @Skip ROWS\r\n      FETCH NEXT @Fetch ROWS ONLY;<\/pre>\n<p><strong>Listing 8: Removing an already fetched row, then fetching next rows<\/strong><\/p>\n<p>In report 9, you can see that the ID value starts at 5 instead of 4, like you may have expected. When rows are inserted, you may end up with the same row returned multiple times.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-96349\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/03\/graphical-user-interface-text-application-descr-3.png\" alt=\"Graphical user interface, text, application\n\nDescription automatically generated\" width=\"365\" height=\"120\" \/><\/p>\n<p><strong>Report 9: Shows that row with ID=4 has been skipped.<\/strong><\/p>\n<p>If you require to get absolutely all of the rows from your <code>SELECT<\/code> statement, it can be useful to store the results in a temporary table and page through it. Another method of handling this is using <code>SNAPSHOT<\/code> isolation level.<\/p>\n<h1>Summary<\/h1>\n<p>In this article you learned how to use the <code>OFFSET<\/code> and <code>FETCH<\/code> options of the <code>ORDER BY<\/code> clause to page through an ordered set of records. The <code>OFFSET<\/code> option was used to skip a specific number of rows in the ordered set. Whereas the <code>FETCH<\/code> option was used to identify the number of rows to <code>FETCH<\/code> from the record set. By controlling these two different options a client application could programmatically page through the rows of data a page at a time. Next time you need to page through a set of rows in a table consider whether using the <code>OFFSET<\/code> and <code>FETCH<\/code> options of the <code>ORDER BY<\/code> clause will meet your paging requirements.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Sometimes when working with very large sets of data, you may not want to return all of the data at once. I discussed using TOP in my previous article, which allowed you to only get a number of rows from the start of the results from a query. However, if you want to see the&#8230;&hellip;<\/p>\n","protected":false},"author":78478,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[53,143525,143531],"tags":[],"coauthors":[11330],"class_list":["post-96331","post","type-post","status-publish","format-standard","hentry","category-featured","category-learn","category-t-sql-programming-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/96331","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/78478"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=96331"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/96331\/revisions"}],"predecessor-version":[{"id":97080,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/96331\/revisions\/97080"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=96331"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=96331"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=96331"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=96331"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}