<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. SQL
  3. Foundations
All lessons in this course

Lesson 6 of 54 15 minBeginner

Sorting and paging: ORDER BY, TOP, OFFSET and DISTINCT

Sort results by one or more columns, take the first few rows, page through a long list and remove duplicates. And why a query without ORDER BY has no order at all.

A table has no built-in order, and neither does a query result, unless you ask for one. Rows often come back in the order they were inserted, which makes it easy to believe that order is guaranteed. It is not: add an index, or let the table grow until SQL Server reads it in parallel, and the order changes. Only ORDER BY guarantees the order of a result.

ORDER BY

ORDER BY sorts by one or more columns, each ASC (smallest first, the default) or DESC (largest first). With several columns, the second sorts only rows that tie on the first:

Query.sql
SELECT Name, CategoryId, PriceFROM dbo.ProductsWHERE CategoryId IN (1, 2, 3)ORDER BY CategoryId, Price DESC;
Output
Name                 CategoryId  Price
-------------------  ----------  -----
Cordless drill                1  89.00
Claw hammer                   1  18.50
Tape measure 8 m              1   9.75
Cement 25 kg                  2   7.20
Sand 25 kg                    2   4.10
Plywood sheet 18 mm           3  42.00
Timber 2x4 2.4 m              3   6.80

ORDER BY is processed after SELECT, so it can use aliases and expressions, and even columns that are not in the SELECT list. NULL counts as smaller than any value, so NULLs come first in ascending order:

Query.sql
SELECT FirstName, City, JoinedOnFROM dbo.CustomersORDER BY City, JoinedOn DESC;
Output
FirstName  City       JoinedOn
---------  ---------  ----------
Ella       NULL       2023-09-14
Dev        Bradford   2023-08-23
Zoë        Harrogate  2024-02-15
Grace      Leeds      2024-01-08
Chloe      Leeds      2023-06-07
Asha       Leeds      2023-03-02
Farid      York       2023-11-30
Ben        York       2023-05-19

TOP, WITH TIES and OFFSET … FETCH

Sort, then choose how many rows to return. Turn on WITH TIES, switch to paging, and watch what happens when the cut falls between two rows with the same price:

Interactive

Sort, take the top, page through

Query.sql
SELECT TOP (3) ProductId, Name, Price, Stock, Unit, AddedOnFROM dbo.ProductsORDER BY Price;
The sorted rows; the ones the query returns are green
ProductIdNamePriceStockUnitAddedOn
15Sand 25 kg4.10220bag2023-03-10
29Wall plugs (100)4.500box2023-05-20
36Timber 2x4 2.4 m6.80500each2023-04-05
412Safety goggles6.800each2023-07-01
54Cement 25 kg7.20300bag2023-03-10
63Tape measure 8 m9.7565each2023-01-15
78Wood screws (200)11.90150box2023-05-20
811Paint roller set12.5030each2023-06-12
91Claw hammer18.5040each2023-01-15
107Plywood sheet 18 mm42.0080sheet2023-04-05
1110Masonry paint 10 L54.0025tin2023-06-12
122Cordless drill89.0012each2023-02-01
Amber rows share Price = 6.80, and the cut falls between them. With no tie-breaker SQL Server may put them in either order, so which one you get can change from run to run. Add “then by ProductId” or WITH TIES.

TOP (n) returns the first n rows of the sorted result. Two products cost 6.80, so “the three cheapest” is ambiguous. WITH TIES settles it by including every row that ties with the last one:

Query.sql
SELECT TOP (3) WITH TIES Name, PriceFROM dbo.ProductsORDER BY Price;
Output
Name              Price
----------------  -----
Sand 25 kg         4.10
Wall plugs (100)   4.50
Timber 2x4 2.4 m   6.80
Safety goggles     6.80

For pages of results, as on a web page with Next and Previous buttons, use OFFSET … FETCH. It goes after ORDER BY and needs it. Here is page 2, four products per page:

Query.sql
SELECT ProductId, Name, PriceFROM dbo.ProductsORDER BY Price, ProductIdOFFSET 4 ROWS FETCH NEXT 4 ROWS ONLY;
Output
ProductId  Name               Price
---------  -----------------  -----
        4  Cement 25 kg        7.20
        3  Tape measure 8 m    9.75
        8  Wood screws (200)  11.90
       11  Paint roller set   12.50

DISTINCT

SELECT DISTINCT removes duplicate rows from the result. Rows count as duplicates only if every column in the SELECT list matches, and DISTINCT treats NULLs as equal to each other:

Query.sql
SELECT DISTINCT CityFROM dbo.CustomersORDER BY City;
Output
City
---------
NULL
Bradford
Harrogate
Leeds
York
Query.sql
SELECT DISTINCT CategoryId, UnitFROM dbo.ProductsWHERE CategoryId <= 3ORDER BY CategoryId;
Output
CategoryId  Unit
----------  -----
         1  each
         2  bag
         3  each
         3  sheet

The full logical order

  1. FROM: the source rows
  2. WHERE: filter rows
  3. SELECT: work out columns and aliases
  4. DISTINCT: remove duplicate rows
  5. ORDER BY: sort
  6. TOP / OFFSET … FETCH: keep only some of the sorted rows

This is why TOP without ORDER BY is a gamble: it takes the first rows of an unsorted result, which can be any rows. The Grouping module adds GROUP BY and HAVING between WHERE and SELECT.

Quick check

A query has no ORDER BY. In what order do the rows come back?

Quick check

In ascending order, where do NULLs appear in SQL Server?

Quick check

Which clause skips 20 rows and returns the next 10?

Practice

Show the three cheapest products that are in stock, cheapest first. Where two cost the same, sort them by name. Return Name, Price and Stock.

Show a hint

Filter with WHERE Stock > 0, sort with ORDER BY Price, Name, and take TOP (3).

Show the solution
Solution.sql
SELECT TOP (3) Name, Price, StockFROM dbo.ProductsWHERE Stock > 0ORDER BY Price, Name;
Output
Name              Price  Stock
----------------  -----  -----
Sand 25 kg         4.10    220
Timber 2x4 2.4 m   6.80    500
Cement 25 kg       7.20    300

Remember

  • Only ORDER BY guarantees the order of a result; without it, any order is possible.
  • Sort by several columns, each ASC or DESC; NULLs sort first in ascending order.
  • TOP (n) takes the first n sorted rows; WITH TIES adds rows that tie with the last one.
  • OFFSET … FETCH pages through results; sort by something unique so pages do not overlap.
  • DISTINCT removes rows that match in every selected column.
  • Logical order: FROM → WHERE → SELECT → DISTINCT → ORDER BY → TOP / OFFSET.
0 of 54 lessons completed