Brightlane's product export tool pages through the catalogue in alphabetical batches of 10. The tool is requesting the batch that starts at position 61.
Write a query to return the ID, name, and price for every product in that batch.
Assumptions:
- The
productstable contains exactly 63 products in Brightlane's catalogue. - The batch starts at position 61, so 60 rows must be skipped —
OFFSET 60, thenLIMIT 10. - Only 3 products remain after position 60, so the batch will be a partial last batch of 3 rows. PostgreSQL returns whatever rows exist within the requested window without padding or error.
Output:
- One row per product in the batch, with columns
id,name, andprice, sorted bynameascending.
Schema · ecommerce 5 tables
Run previews · Check grades
Write a query, then run it to see results here.
Worked solution Try it yourself first
SELECT
id,
name,
price
FROM
products
ORDER BY
name
LIMIT
10
OFFSET
60 The shape
The export tool asks for a batch of 10 starting at row 61, but only 3 products remain past row 60 — LIMIT 10 OFFSET 60 returns those 3 rows and stops without error, which is the last-page shape in every paginated feed.
Clause by clause
SELECT id, name, pricepicks the three columns the export needs per row.FROM productsreads the full catalogue — 63 products in total.ORDER BY namesorts alphabetically. The same key has anchored every prior batch, which is what makes "batch starting at position 61" mean the same thing run after run.LIMIT 10 OFFSET 60requests up to 10 rows after skipping the first 60. The sorted result has 63 rows, soOFFSET 60lands on row 61 with three rows remaining: Writing Clean Code, Yoga Mat, and Zephyr 12. PostgreSQL returns those three and stops — it doesn't loop back, pad with nulls, or raise an error.
Why this and not a separate query for the partial batch
The export tool doesn't need to know in advance that the last batch is partial. The same query shape — LIMIT 10 OFFSET <page * 10> — handles full batches and the partial trailing batch identically. PostgreSQL evaluates the window, returns whatever rows fall inside it, and the result size is what tells the tool the export is done.
That behaviour is what makes LIMIT work as a ceiling rather than a floor. The clause says "return at most this many," not "return exactly this many." If fewer rows exist past the OFFSET, the smaller set comes back. If more exist, the cap kicks in. Same query, both cases.
The trap
A learner who reads LIMIT 10 as "return 10 rows" can be surprised when the result has 3. The cap is an upper bound, not a contract. The same misread shows up the other direction too — assuming a partial result means something went wrong, when in fact it means the dataset is smaller than the window the query asked for. For an export tool, the right read is the opposite: a result with fewer rows than the LIMIT is the signal that the end of the data has been reached, and the next batch would be empty.
You practiced requesting a window that runs past the end of the data. LIMIT is a ceiling, not a floor — when fewer rows exist after the OFFSET than LIMIT allows, PostgreSQL returns the partial set and stops, which is the recurring shape of the last page in any paginated feed.