Using ‘FETCH FIRST’ and ‘OFFSET’ in PostgreSQL

Updated: January 4, 2024 By: Guest Contributor Post a comment

Introduction

The SQL clauses ‘FETCH FIRST’ and ‘OFFSET’ are PostgreSQL features designed to control the number of records returned by a query, supporting the implementation of pagination. This tutorial illustrates their usage through step-by-step examples.

Understanding FETCH FIRST

The ‘FETCH FIRST’ clause in PostgreSQL is used to limit the number of rows returned in a query’s result set. It is often combined with ‘ORDER BY’ to ensure a consistent output. Here’s a simple example:

SELECT * FROM products
ORDER BY price ASC
FETCH FIRST 10 ROWS ONLY;

This query returns the top 10 cheapest products.

Implementing Pagination with OFFSET

To skip a specific number of records, the ‘OFFSET’ clause is used in conjunction with ‘FETCH FIRST’. Here is a basic pagination example:

SELECT * FROM products
ORDER BY price ASC
OFFSET 10 ROWS
FETCH FIRST 10 ROWS ONLY;

This skips the first 10 products and then gets the next 10.

Combining Fetch and Offset Efficiently

In pagination, it’s important to balance performance with functionality. When you have a large dataset, consider indexed columns for sorting. Here’s a more performant query:

SELECT id, name FROM products
WHERE id > (SELECT id FROM products ORDER BY id LIMIT 1 OFFSET 100)
ORDER BY id ASC
LIMIT 10;

This uses a subquery to exclude the first 100 records before fetching the next 10.

Advanced Scenarios: Conditional Pagination

Pagination can become more complex when conditional logic is needed. The following example demonstrates this:

WITH ranked_products AS (
SELECT id, name, ROW_NUMBER() OVER (ORDER BY price DESC) AS rank
FROM products
WHERE category_id = 2
)
SELECT id, name FROM ranked_products
WHERE rank BETWEEN 31 AND 40;

Here we use a window function to establish a rank for pagination within a specific category.

Handling Large Offsets

For larger offsets, ‘OFFSET’ can be inefficient as it still scans through all earlier rows. A keyset pagination approach is more efficient:

SELECT * FROM products
WHERE id > (SELECT id FROM products WHERE category_id = 2 ORDER BY id ASC LIMIT 1 OFFSET 999)
ORDER BY id ASC
LIMIT 10;

This method jumps directly to the target subset of data.

FETCH and OFFSET in Joins and Subqueries

When joining tables or using subqueries, ‘FETCH’ and ‘OFFSET’ apply to the final result set. Take care to ensure proper ordering and limiting:

SELECT p.name, c.name
FROM products p
JOIN categories c ON p.category_id = c.id
ORDER BY p.price ASC
OFFSET 20 ROWS
FETCH FIRST 10 ROWS ONLY;

Complex queries may require additional consideration for ordering and uniqueness.

Best Practices

Best Practices for Using FETCH and OFFSET in production projects:

  • Always use ‘ORDER BY’ to ensure a predictable order.
  • Be aware of the performance implications on large datasets.
  • Utilize indexes to optimize the OFFSET fetch sequence.
  • Consider cursor-based pagination as an alternative to ‘OFFSET’ for performance.

Conclusion

Using ‘FETCH FIRST’ and ‘OFFSET’ in PostgreSQL provides precise control over result sets and is crucial for implementing features like pagination. While powerful, it’s important to recognize potential performance issues and apply best practices for efficient database querying.