SQL LIMIT and OFFSET to Limit Query Results
Up to this point, every query you have written returns all matching rows. If your table has 10 products, you get 10 rows back. But what happens when your table has 10,000 products, or a million orders? Retrieving everything is slow, wasteful, and rarely what your application actually needs.
In practice, you almost always want a subset of results. The top 5 best sellers. The 10 most recent orders. Page 3 of search results showing items 21 through 30. This is where LIMIT and OFFSET come in.
LIMIT controls how many rows the database returns. OFFSET tells the database how many rows to skip before starting to return results. Together, they power some of the most common features in modern applications: top-N lists, pagination, dashboards, and preview panels.
This guide covers LIMIT and OFFSET in depth, explains the syntax differences across database systems (LIMIT vs TOP vs FETCH FIRST), walks through real-world pagination, and demonstrates practical use cases with the ShopSmart sample database (we defined it in a previous guide here).
How LIMIT Works
The LIMIT clause restricts the maximum number of rows returned by a query. It goes at the very end of your statement, after ORDER BY:
SELECT columns
FROM table
WHERE condition -- optional
ORDER BY column -- optional but recommended
LIMIT number;
Here is the simplest example. Retrieve only 3 products:
SELECT name, price
FROM products
LIMIT 3;
Output:
| name | price |
|---|---|
| Wireless Mouse | 29.99 |
| Mechanical Keyboard | 89.99 |
| USB-C Hub | 45.00 |
The products table has 10 rows, but the query returns only 3. The database stops after finding the first 3 rows that match.
In the example above, the 3 rows returned are not necessarily the "first" 3 in any meaningful sense. Without ORDER BY, the database returns rows in whatever internal order it finds them. That order can change over time.
-- Unpredictable: Which 3 products will you get?
SELECT name, price
FROM products
LIMIT 3;
-- Predictable: The 3 cheapest products, guaranteed
SELECT name, price
FROM products
ORDER BY price ASC
LIMIT 3;
Always combine LIMIT with ORDER BY unless the specific rows returned truly do not matter.
LIMIT with ORDER BY: The Core Pattern
The most common and most useful pattern is ORDER BY combined with LIMIT. This answers questions like "show me the top N" or "show me the bottom N":
The 5 Most Expensive Products
SELECT name, price
FROM products
ORDER BY price DESC
LIMIT 5;
Output:
| name | price |
|---|---|
| Coffee Maker Pro | 129.99 |
| Running Shoes X1 | 110.00 |
| Mechanical Keyboard | 89.99 |
| Bluetooth Speaker | 65.00 |
| USB-C Hub | 45.00 |
Sort by price descending (highest first), then take only the first 5 rows. The result is the top 5 most expensive products.
The 3 Cheapest Available Products
SELECT name, price
FROM products
WHERE is_available = true
ORDER BY price ASC
LIMIT 3;
Output:
| name | price |
|---|---|
| Stainless Water Bottle | 24.99 |
| Wireless Mouse | 29.99 |
| SQL for Beginners | 34.99 |
The WHERE clause filters to available products, ORDER BY sorts by price ascending (cheapest first), and LIMIT takes only the first 3.
The Most Recent Order
SELECT id, order_date, total_amount, status
FROM orders
ORDER BY order_date DESC
LIMIT 1;
Output:
| id | order_date | total_amount | status |
|---|---|---|---|
| 8 | 2024-04-10 | 199.98 | pending |
LIMIT 1 returns exactly one row, which is the single most recent order.
The 3 Oldest Customer Accounts
SELECT first_name, last_name, signup_date
FROM customers
ORDER BY signup_date ASC
LIMIT 3;
Output:
| first_name | last_name | signup_date |
|---|---|---|
| Alice | Johnson | 2023-01-15 |
| Bob | Martinez | 2023-03-22 |
| Carol | Singh | 2023-06-10 |
Imagine you have a sorted list of 100 items written on paper. LIMIT 5 means you tear off the paper after the 5th item and throw away the rest. You still get them in the sorted order, but you only get the first 5.
How OFFSET Works
OFFSET tells the database to skip a specified number of rows before starting to return results. It is almost always used together with LIMIT.
SELECT columns
FROM table
ORDER BY column
LIMIT number
OFFSET skip_count;
Here is a simple demonstration. Get 3 products, but skip the first 2:
SELECT name, price
FROM products
ORDER BY price ASC
LIMIT 3
OFFSET 2;
Output:
| name | price |
|---|---|
| SQL for Beginners | 34.99 |
| Yoga Mat Premium | 38.00 |
| Data Science Handbook | 42.50 |
The database sorted all products by price (ascending), skipped the first 2 rows (Stainless Water Bottle at $24.99 and Wireless Mouse at $29.99), and then returned the next 3 rows.
Visualizing OFFSET
Consider all products sorted by price ascending:
| Row # | name | price | OFFSET 0 | OFFSET 2 | OFFSET 5 |
|---|---|---|---|---|---|
| 1 | Stainless Water Bottle | 24.99 | ✅ | skipped | skipped |
| 2 | Wireless Mouse | 29.99 | ✅ | skipped | skipped |
| 3 | SQL for Beginners | 34.99 | ✅ | ✅ | skipped |
| 4 | Yoga Mat Premium | 38.00 | ✅ | skipped | |
| 5 | Data Science Handbook | 42.50 | ✅ | skipped | |
| 6 | USB-C Hub | 45.00 | ✅ | ||
| 7 | Bluetooth Speaker | 65.00 | ✅ | ||
| 8 | Mechanical Keyboard | 89.99 | ✅ | ||
| 9 | Running Shoes X1 | 110.00 | |||
| 10 | Coffee Maker Pro | 129.99 |
With LIMIT 3 OFFSET 0, you get rows 1, 2, 3. With LIMIT 3 OFFSET 2, you get rows 3, 4, 5. With LIMIT 3 OFFSET 5, you get rows 6, 7, 8.
Pagination: The Real-World Use Case
The most important practical application of LIMIT and OFFSET is pagination, the process of dividing a large result set into smaller "pages" that users can navigate through.
Think of any e-commerce website, search engine, or social media feed. They do not load all 50,000 products or all 10 million posts at once. They show you one page at a time (typically 10, 20, or 25 items) and let you click "Next" to see more.
Building Pagination Step by Step
Imagine your product listing page shows 3 products per page (we use 3 for simplicity; real applications typically use 10, 20, or 25).
Page 1 (first 3 products):
SELECT name, price
FROM products
ORDER BY name ASC
LIMIT 3
OFFSET 0;
Output:
| name | price |
|---|---|
| Bluetooth Speaker | 65.00 |
| Coffee Maker Pro | 129.99 |
| Data Science Handbook | 42.50 |
Page 2 (skip the first 3, show the next 3):
SELECT name, price
FROM products
ORDER BY name ASC
LIMIT 3
OFFSET 3;
Output:
| name | price |
|---|---|
| Mechanical Keyboard | 89.99 |
| Running Shoes X1 | 110.00 |
| SQL for Beginners | 34.99 |
Page 3 (skip the first 6, show the next 3):
SELECT name, price
FROM products
ORDER BY name ASC
LIMIT 3
OFFSET 6;
Output:
| name | price |
|---|---|
| Stainless Water Bottle | 24.99 |
| USB-C Hub | 45.00 |
| Wireless Mouse | 29.99 |
Page 4 (skip the first 9, show the next 3):
SELECT name, price
FROM products
ORDER BY name ASC
LIMIT 3
OFFSET 9;
Output:
| name | price |
|---|---|
| Yoga Mat Premium | 38.00 |
The last page has only 1 product because there are 10 products total. The database returns whatever remains after the offset, even if it is fewer rows than the limit.
The Pagination Formula
The pattern for calculating the OFFSET for any page is:
OFFSET = (page_number - 1) × items_per_page
| Page | Items Per Page | OFFSET Calculation | OFFSET Value |
|---|---|---|---|
| 1 | 10 | (1 - 1) × 10 | 0 |
| 2 | 10 | (2 - 1) × 10 | 10 |
| 3 | 10 | (3 - 1) × 10 | 20 |
| 4 | 10 | (4 - 1) × 10 | 30 |
| 5 | 10 | (5 - 1) × 10 | 40 |
In application code, this typically looks something like:
-- Generic pagination query
SELECT name, price
FROM products
ORDER BY name ASC
LIMIT 10
OFFSET 20; -- This would be page 3 with 10 items per page
While LIMIT/OFFSET pagination is simple and intuitive, it has a significant performance problem with large datasets. When you write OFFSET 100000, the database must internally scan and discard 100,000 rows before returning your results. The higher the offset, the slower the query.
-- Fast: Skipping 0 rows
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 0;
-- Slower: The database must scan through 10,000 rows and discard them
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 10000;
-- Much slower: Scanning and discarding 1,000,000 rows
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 1000000;
For small to medium datasets (tens of thousands of rows), OFFSET pagination works perfectly fine. For very large datasets (millions of rows), more advanced techniques like keyset pagination (also called "cursor-based pagination") are preferred. That is a topic for a more advanced guide.
Syntax Across Different Databases
One of the notable differences between SQL dialects is how they express result limiting. The concept is the same everywhere, but the syntax varies.
MySQL and PostgreSQL: LIMIT / OFFSET
MySQL and PostgreSQL both use the LIMIT and OFFSET keywords:
-- PostgreSQL and MySQL: identical syntax
SELECT name, price
FROM products
ORDER BY price DESC
LIMIT 5
OFFSET 10;
MySQL also supports an alternative comma syntax where OFFSET comes first:
-- MySQL alternative syntax: LIMIT offset, count
SELECT name, price
FROM products
ORDER BY price DESC
LIMIT 10, 5; -- Skip 10, return 5
The MySQL comma syntax puts the offset before the limit, which is the reverse of the LIMIT...OFFSET order. This easily leads to mistakes.
-- MySQL comma syntax: LIMIT offset, count
LIMIT 10, 5 -- Skip 10 rows, then return 5 rows
-- LIMIT...OFFSET syntax: LIMIT count OFFSET skip
LIMIT 5 OFFSET 10 -- Return 5 rows, skipping the first 10
-- Both produce the same result, but the comma version is easy to misread
Prefer the LIMIT...OFFSET syntax for clarity. It works in both MySQL and PostgreSQL and reads more naturally.
SQLite: LIMIT / OFFSET
SQLite uses the same syntax as MySQL and PostgreSQL:
-- SQLite: same as MySQL/PostgreSQL
SELECT name, price
FROM products
ORDER BY price DESC
LIMIT 5
OFFSET 10;
SQL Server: TOP and OFFSET/FETCH
SQL Server has its own approach. For simple "top N" queries, it uses the TOP keyword placed after SELECT:
-- SQL Server: TOP for simple limiting
SELECT TOP 5 name, price
FROM products
ORDER BY price DESC;
For pagination (with an offset), SQL Server uses the OFFSET...FETCH syntax, which is actually part of the ANSI SQL standard:
-- SQL Server: OFFSET/FETCH for pagination
SELECT name, price
FROM products
ORDER BY price DESC
OFFSET 10 ROWS
FETCH NEXT 5 ROWS ONLY;
In SQL Server, OFFSET...FETCH requires an ORDER BY clause. You cannot use it without specifying a sort order. This is actually a good thing because, as discussed earlier, using LIMIT or OFFSET without ORDER BY gives unpredictable results anyway.
ANSI SQL Standard: FETCH FIRST
The official ANSI SQL standard defines the FETCH FIRST syntax, which works in PostgreSQL, SQL Server, Oracle, and DB2:
-- ANSI SQL standard syntax
SELECT name, price
FROM products
ORDER BY price DESC
FETCH FIRST 5 ROWS ONLY;
-- With OFFSET (ANSI standard)
SELECT name, price
FROM products
ORDER BY price DESC
OFFSET 10 ROWS
FETCH NEXT 5 ROWS ONLY;
Quick Reference: Syntax Comparison
| Database | Top 5 Products | Skip 10, Get 5 |
|---|---|---|
| MySQL | LIMIT 5 | LIMIT 5 OFFSET 10 |
| PostgreSQL | LIMIT 5 | LIMIT 5 OFFSET 10 |
| SQLite | LIMIT 5 | LIMIT 5 OFFSET 10 |
| SQL Server | SELECT TOP 5 ... | OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY |
| ANSI SQL | FETCH FIRST 5 ROWS ONLY | OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY |
If you are working with MySQL, PostgreSQL, or SQLite, use LIMIT/OFFSET. It is clean, concise, and universally understood in these systems. If you are working with SQL Server, use TOP for simple queries and OFFSET...FETCH for pagination. If you want maximum portability, the OFFSET...FETCH syntax is the closest to the ANSI standard and works in PostgreSQL, SQL Server, and Oracle.
Practical Use Case: "Show the 10 Most Expensive Products"
Let us walk through a complete real-world scenario. You are building a product showcase section for an e-commerce homepage that highlights the store's premium products.
Step 1: Get All Products Sorted by Price
First, see the full sorted list to understand your data:
SELECT name, price, category_id, is_available
FROM products
ORDER BY price DESC;
Output:
| name | price | category_id | is_available |
|---|---|---|---|
| Coffee Maker Pro | 129.99 | 3 | true |
| Running Shoes X1 | 110.00 | 4 | true |
| Mechanical Keyboard | 89.99 | 1 | true |
| Bluetooth Speaker | 65.00 | 1 | false |
| USB-C Hub | 45.00 | 1 | true |
| Data Science Handbook | 42.50 | 2 | true |
| Yoga Mat Premium | 38.00 | 4 | true |
| SQL for Beginners | 34.99 | 2 | true |
| Wireless Mouse | 29.99 | 1 | true |
| Stainless Water Bottle | 24.99 | 4 | true |
Step 2: Limit to Top 5
Now take only the 5 most expensive:
SELECT name, price
FROM products
ORDER BY price DESC
LIMIT 5;
Output:
| name | price |
|---|---|
| Coffee Maker Pro | 129.99 |
| Running Shoes X1 | 110.00 |
| Mechanical Keyboard | 89.99 |
| Bluetooth Speaker | 65.00 |
| USB-C Hub | 45.00 |
Step 3: Refine with Business Logic
The showcase should only display products that are actually available for purchase. The Bluetooth Speaker is out of stock, so let us filter it out:
SELECT name, price
FROM products
WHERE is_available = true
ORDER BY price DESC
LIMIT 5;
Output:
| name | price |
|---|---|
| Coffee Maker Pro | 129.99 |
| Running Shoes X1 | 110.00 |
| Mechanical Keyboard | 89.99 |
| USB-C Hub | 45.00 |
| Data Science Handbook | 42.50 |
Now the Bluetooth Speaker is gone, and the Data Science Handbook has moved up to fill the 5th spot. This is the query your application would use to populate the "Premium Products" section.
Step 4: Add Category Context
For an even richer display, join with the categories table:
SELECT p.name, p.price, c.name AS category
FROM products p
JOIN categories c ON p.category_id = c.id
WHERE p.is_available = true
ORDER BY p.price DESC
LIMIT 5;
Output:
| name | price | category |
|---|---|---|
| Coffee Maker Pro | 129.99 | Home & Kitchen |
| Running Shoes X1 | 110.00 | Sports |
| Mechanical Keyboard | 89.99 | Electronics |
| USB-C Hub | 45.00 | Electronics |
| Data Science Handbook | 42.50 | Books |
This is a production-ready query that could power a real product showcase widget.
More Practical Patterns
The Single Newest Record
A very common pattern is finding the single most recent entry in a table:
-- The most recent order
SELECT id, order_date, total_amount, status
FROM orders
ORDER BY order_date DESC
LIMIT 1;
Output:
| id | order_date | total_amount | status |
|---|---|---|---|
| 8 | 2024-04-10 | 199.98 | pending |
The Largest Single Order
-- The order with the highest total
SELECT id, order_date, total_amount, status
FROM orders
ORDER BY total_amount DESC
LIMIT 1;
Output:
| id | order_date | total_amount | status |
|---|---|---|---|
| 8 | 2024-04-10 | 199.98 | pending |
Top 3 Customers by Signup Date (Newest First)
SELECT first_name, last_name, signup_date
FROM customers
ORDER BY signup_date DESC
LIMIT 3;
Output:
| first_name | last_name | signup_date |
|---|---|---|
| Frank | Wilson | 2024-02-28 |
| Eva | Brown | 2024-01-18 |
| David | Chen | 2023-08-05 |
Bottom 3 Products by Stock
-- Products running lowest on stock
SELECT name, stock_quantity, price
FROM products
WHERE is_available = true
ORDER BY stock_quantity ASC
LIMIT 3;
Output:
| name | stock_quantity | price |
|---|---|---|
| Coffee Maker Pro | 25 | 129.99 |
| Data Science Handbook | 30 | 42.50 |
| SQL for Beginners | 50 | 34.99 |
This is the kind of query an inventory management system would use to generate a "low stock alert" report.
Sample a Few Rows from a Large Table
Sometimes you just want to peek at a few rows to understand a table's contents:
-- Quick peek at the order_items table
SELECT *
FROM order_items
LIMIT 5;
Output:
| id | order_id | product_id | quantity | unit_price |
|---|---|---|---|---|
| 1 | 1 | 1 | 2 | 29.99 |
| 2 | 1 | 3 | 1 | 45.00 |
| 3 | 2 | 2 | 1 | 89.99 |
| 4 | 3 | 4 | 1 | 34.99 |
| 5 | 3 | 10 | 1 | 24.99 |
This is one of the few cases where LIMIT without ORDER BY is acceptable since you are just exploring, not building a feature.
OFFSET Edge Cases
Understanding a few edge cases will help you avoid confusion with OFFSET.
OFFSET Beyond Available Rows
If OFFSET exceeds the total number of rows, the query returns an empty result set, not an error:
-- Only 10 products exist, but we skip 50
SELECT name, price
FROM products
ORDER BY price
LIMIT 5
OFFSET 50;
Output:
(empty result set, zero rows)
This is normal and expected behavior. Your application should handle this gracefully, typically by displaying a "No more results" message.
OFFSET 0
OFFSET 0 is the same as no offset at all. It skips zero rows:
-- These two queries are identical
SELECT name, price FROM products ORDER BY price LIMIT 5;
SELECT name, price FROM products ORDER BY price LIMIT 5 OFFSET 0;
LIMIT Without OFFSET
When you use LIMIT without OFFSET, the offset defaults to 0:
-- These are equivalent
SELECT name FROM products LIMIT 5;
SELECT name FROM products LIMIT 5 OFFSET 0;
OFFSET Without LIMIT
In PostgreSQL and SQLite, you can use OFFSET without LIMIT, which skips rows but returns everything after that:
-- PostgreSQL/SQLite: Skip the first 7 products, return the rest
SELECT name, price
FROM products
ORDER BY price ASC
OFFSET 7;
Output:
| name | price |
|---|---|
| Mechanical Keyboard | 89.99 |
| Running Shoes X1 | 110.00 |
| Coffee Maker Pro | 129.99 |
In MySQL, OFFSET without LIMIT is not allowed. You would need to provide an arbitrarily large LIMIT value:
-- MySQL: OFFSET requires LIMIT, so use a very large number
SELECT name, price
FROM products
ORDER BY price ASC
LIMIT 999999999
OFFSET 7;
Combining LIMIT with Everything You Have Learned
Here are a few comprehensive examples that combine LIMIT and OFFSET with WHERE, AND/OR, and ORDER BY:
The 3 Most Expensive Available Electronics
SELECT name, price
FROM products
WHERE category_id = 1
AND is_available = true
ORDER BY price DESC
LIMIT 3;
Output:
| name | price |
|---|---|
| Mechanical Keyboard | 89.99 |
| USB-C Hub | 45.00 |
| Wireless Mouse | 29.99 |
The 2 Most Recent Completed Orders Over $80
SELECT id, order_date, total_amount
FROM orders
WHERE status = 'completed'
AND total_amount > 80
ORDER BY order_date DESC
LIMIT 2;
Output:
| id | order_date | total_amount |
|---|---|---|
| 2 | 2024-01-15 | 89.99 |
| 1 | 2024-01-10 | 119.98 |
Paginated Order History (Page 2, 3 Items Per Page)
SELECT id, order_date, total_amount, status
FROM orders
ORDER BY order_date DESC
LIMIT 3
OFFSET 3;
Output:
| id | order_date | total_amount | status |
|---|---|---|---|
| 5 | 2024-03-12 | 155.00 | shipped |
| 4 | 2024-03-05 | 129.99 | shipped |
| 3 | 2024-02-20 | 77.49 | completed |
Practical Exercises
Exercise 1
Find the single cheapest product in the store.
SELECT name, price
FROM products
ORDER BY price ASC
LIMIT 1;
Expected output:
| name | price |
|---|---|
| Stainless Water Bottle | 24.99 |
Exercise 2
Find the 3 most recent orders that are still pending.
SELECT id, order_date, total_amount
FROM orders
WHERE status = 'pending'
ORDER BY order_date DESC
LIMIT 3;
Expected output:
| id | order_date | total_amount |
|---|---|---|
| 8 | 2024-04-10 | 199.98 |
| 7 | 2024-04-01 | 63.00 |
| 6 | 2024-03-20 | 34.99 |
Exercise 3
Display page 2 of the product list (sorted by name, 4 items per page).
SELECT name, price
FROM products
ORDER BY name ASC
LIMIT 4
OFFSET 4;
Expected output:
| name | price |
|---|---|
| Running Shoes X1 | 110.00 |
| SQL for Beginners | 34.99 |
| Stainless Water Bottle | 24.99 |
| USB-C Hub | 45.00 |
Exercise 4
Find the 2 customers who have been members the longest.
SELECT first_name, last_name, signup_date
FROM customers
ORDER BY signup_date ASC
LIMIT 2;
Expected output:
| first_name | last_name | signup_date |
|---|---|---|
| Alice | Johnson | 2023-01-15 |
| Bob | Martinez | 2023-03-22 |
Exercise 5
Find the top 3 highest-value orders that are either shipped or completed.
SELECT id, order_date, total_amount, status
FROM orders
WHERE status = 'shipped' OR status = 'completed'
ORDER BY total_amount DESC
LIMIT 3;
Expected output:
| id | order_date | total_amount | status |
|---|---|---|---|
| 5 | 2024-03-12 | 155.00 | shipped |
| 4 | 2024-03-05 | 129.99 | shipped |
| 1 | 2024-01-10 | 119.98 | completed |
Key Takeaways
LIMIT and OFFSET give you precise control over how many rows your query returns and which portion of the results you see. Here is what you should remember:
LIMITrestricts the maximum number of rows returnedOFFSETskips a specified number of rows before returning results- Always use
ORDER BYwithLIMITto ensure predictable, meaningful results - The clause order is:
SELECT → FROM → WHERE → ORDER BY → LIMIT → OFFSET - Pagination formula:
OFFSET = (page_number - 1) × items_per_page - Different databases use different syntax:
LIMIT(MySQL, PostgreSQL, SQLite),TOP(SQL Server), andFETCH FIRST(ANSI standard) OFFSETbeyond available rows returns an empty result set, not an errorOFFSETpagination becomes slow on large datasets because the database must scan and discard all skipped rows- The
LIMIT 1pattern combined withORDER BYis the standard way to find the single "most" or "least" of anything LIMITwithoutORDER BYis only appropriate for quick data exploration, never for application features
With SELECT, FROM, WHERE, logical operators, ORDER BY, LIMIT, and OFFSET, you now have a complete toolkit for writing targeted, sorted, paginated queries. These clauses form the backbone of the vast majority of SQL queries in production applications.