← All NukteSELECT * FROM TheShiraverse
Module 1 › Sorting & Pagination

Sorting & Pagination: ORDER BY, LIMIT & OFFSET

Module 1, Session 8. ORDER BY with ASC/DESC and tie-breakers, NULLS FIRST/LAST, LIMIT and OFFSET pages, keyset pagination and the order PostgreSQL really runs a query in.

Jo class mein bola, yahan nukte mein likha hai.
What I said in class, written here as key points.

Sorting & Pagination

Put rows in the order people need, decide where missing values go, and hand results out one page at a time: ORDER BY, NULLS FIRST / LAST, LIMIT, OFFSET, keyset pages, and the order PostgreSQL really runs a query in.

24
Interactive Scenes
1
Practice Arena
4
Hands-on Drills
5
Interview Scenarios

Today’s Agenda — Sorting & Pagination

Four pillars: the right rows, in the right order, one page at a time

Pillar 01
📊 Sorting
01 The Sale Day Story: Search Results
02 ORDER BY — ASC & DESC
03 Tie-Breakers & NULLS FIRST / LAST
Pillar 02
📄 Pages
04 LIMIT & OFFSET
05 Practice Arena 03
06 Why Page 5,000 Is Slow: Keyset Pages
Pillar 03
⚙️ Execution Order
07 The 6-Step Pipeline
08 Why an Alias Fails in WHERE
09 6 Classic Traps
Pillar 04
🎯 Practice & Next
10 RetailMart Exploration Queries
11 Interview Questions & Self-Check
12 Next class: String Functions
💡
Why this matters: sales.orders has 1,50,000 rows. No app shows them all: every search page, feed and “top 10” sorts the rows first and then shows 20 at a time. That is today’s skill.

Where We Left Off

From the right rows to the right order

🔍 SESSION 07 • COMPLETED

What Filtering Solved

-- Session 7: exactly the rows we need
SELECT store_id, city, square_ft
FROM stores.stores
WHERE (city = 'Noida' OR city = 'Prayagraj')
AND square_ft > 3000;
-- 5 stores (no brackets: 9)
Last class we picked the right rows: WHERE, IS NULL, AND/OR with brackets, LIKE, BETWEEN, IN and DISTINCT.
✓ WHERE + brackets: the right rows
📊 SESSION 08 • THE MISSION

LIMIT 10 Is Not “Top 10”

-- “Show me our 10 biggest orders.” We run:
SELECT order_id, net_total
FROM sales.orders
LIMIT 10;
⚠️ Orders 1 to 10. The biggest of them: ₹3,00,772. The real biggest order: ₹11,11,861.
LIMIT gives the first rows PostgreSQL finds, not the top ones. “Top” needs a sort. Today: ORDER BY, then pages.
📊 Today: ORDER BY, NULLS LAST, LIMIT, OFFSET, keyset
⚖️
The Core Distinction: WHERE decides which rows come back. ORDER BY decides in what order, and LIMIT/OFFSET decide how many, from where. Without ORDER BY, rows have no order at all.
🛒
Sale Day: The Search Results Page SORTED WRONG, PAGED WRONG
SHOPPING APP • SEARCH TEAM EXAMPLE STORY • APP & NUMBERS MADE UP 🕘 SALE STARTS AT 12:00
STEP 01 • THE COMPLAINT
customer-support 12:04 PM
“Users searching ‘phone’ with Price: High to Low see blank prices first, the same phone on page 1 and page 2, and page 500 just spins.”
Lakhs of people are on the app at once. Every search is one SQL query: sort the rows, cut out 20.
STEP 02 • THREE BUGS
📱 Price High to Low: No Prices on Top
No NULLS LAST
The first ten results are “coming soon” phones with no price. In a descending sort, PostgreSQL puts NULLs first.
🔁 The Same Phone on Two Pages
No Tie-Breaker
Forty phones cost exactly ₹9,999. Sorted only by price, they come back in a different order per page: one shows twice, another never.
🐢 Page 500 Never Loads
Deep OFFSET
OFFSET 9980 makes the database read and throw away 9,980 rows to return 20, for every page. The site slows for everyone.
STEP 03 • THE FIX
ONE QUERY
🐙 Sorted, Stable, Paged
SELECT product_id, product_name, price
FROM products.products
ORDER BY price DESC NULLS LAST,
         product_id
LIMIT 20;
-- next page: start after the last row
--   shown (keyset pages, scene 9)
✓ NULLs last, ties fixed Page 500 as fast as page 1
STEP 04 • CORE PRINCIPLE
The Moral: A sort needs three decisions: the direction (DESC), where the unknowns go (NULLS LAST), and a unique last column so every row has one place (product_id). Deep pages need the last row’s key, not a huge OFFSET.

From the Search Page to SQL Clauses

Every Fix Has a Clause
ROSETTA STONE • 6 CLAUSES

Each thing the search page needs maps to one piece of the query:

What the Page Needs SQL Clause What PostgreSQL Does
📊 Sort by price ORDER BY price Without ORDER BY there is no order at all; with it, the default is ASC (smallest first).
⬇️ High to Low DESC Reverses one column. In ORDER BY a, b DESC, DESC belongs to b only.
❓ Blank prices at the bottom NULLS LAST PostgreSQL treats NULL as bigger than every value, so DESC puts NULLs first unless you say otherwise.
🔁 Same phone never twice , product_id A unique last column breaks every tie: each row gets exactly one place.
📄 20 results per page LIMIT 20 OFFSET 40 LIMIT = how many rows, OFFSET = how many to skip first. This is page 3.
🐢 Page 500 without the wait WHERE id > last_id Keyset: start after the last row you showed instead of skipping thousands.
⚠️
The One Trap to Remember: LIMIT and OFFSET without ORDER BY cut pages out of an order that does not exist. Same query, different rows, repeats and gaps. Always sort before you page.

ORDER BY: The Sorting Crane & NULL Placement

How PostgreSQL orders numeric values and handles NULL values in ASC and DESC directions.

⚙️ SIMULATE ORDERING:
CURRENT STATE: UNSORTED PRODUCT STREAM
PRODUCT #1
₹850
PRODUCT #2
NULL
PRODUCT #3
₹4,500
PRODUCT #4
₹120
PRODUCT #5
₹15,000
Standby • Click a sort order button to observe how PostgreSQL positions NULL values. POSTGRESQL DEFAULT

Definition: ORDER BY & Multi-Column Sorting

Sort by one column, break ties with a second, and decide where NULLs go

📊 1. No Order Without ORDER BY

Rows come back in whatever order is cheapest for PostgreSQL. Only ORDER BY promises an order. Default is ASC.

🧭 2. Several Columns, Left to Right

ORDER BY brand_id, price DESC: brand decides first; price only orders rows inside the same brand.

⚖️ 3. NULLs Are “Biggest”

ASC puts NULLs last, DESC puts them first. Choose with NULLS FIRST / NULLS LAST.

ORDER BY on RetailMart
PostgreSQL
STEP 1DESC: most expensive first ▶ Run
SELECT product_id, product_name, price
FROM products.products
ORDER BY price DESC
LIMIT 10;
→ Top: HP Deserunt X, ₹1,49,578.93 (of 6,036)
STEP 2Brand first, then price ▶ Run
SELECT product_name, brand_id, price
FROM products.products
ORDER BY brand_id ASC,
         price DESC
LIMIT 10;
→ Brand 1 first, its priciest ₹1,49,041.53
NULLSLatest delivery, blanks last ▶ Run
SELECT shipment_id, delivered_date
FROM sales.shipments
ORDER BY delivered_date DESC
         NULLS LAST
LIMIT 10;
→ Without NULLS LAST: 14,982 blank rows on top

LIMIT & OFFSET: Sliding Window Engine

LIMIT cuts one page, OFFSET skips rows first. Click a page and watch how many rows PostgreSQL reads.

⚙️ SLICE WINDOW:
WINDOW: sales.orders • 1,50,000 ROWS AVAILABLE
Row 0 (Start) 0 Rows Read Row 1,50,000 (End)
Page 1 • Reads 10 rows and returns them.
Click a page to see how many rows PostgreSQL reads for it. FAST

Definition: LIMIT & OFFSET

Cut a sorted result into pages, and know when OFFSET gets slow

📄 1. LIMIT: How Many

LIMIT 20 returns at most 20 rows. On its own it gives the first 20 found, not the top 20.

⏭️ 2. OFFSET: Skip First

LIMIT 20 OFFSET 40 skips 40 rows and returns the next 20: page 3. Always with ORDER BY.

🐢 3. Deep Pages: Keyset

OFFSET 1,00,000 reads and discards 1,00,000 rows. Keyset (WHERE id > last_id) starts right after the last row.

Pages of customers.customers (50,000 rows)
PostgreSQL
PAGE 1LIMIT: the first page ▶ Run
SELECT customer_id, first_name
FROM customers.customers
ORDER BY customer_id
LIMIT 20;
→ customer_id 1 to 20
PAGE 3OFFSET: skip 40 ▶ Run
SELECT customer_id, first_name
FROM customers.customers
ORDER BY customer_id
LIMIT 20 OFFSET 40;
→ customer_id 41 (Veer) to 60
KEYSETAfter the last id seen ▶ Run
SELECT customer_id, first_name
FROM customers.customers
WHERE customer_id > 1042
ORDER BY customer_id
LIMIT 20;
→ customer_id 1043 (Avani) to 1062
65%

Query Mode: Unlocked — Sorting & Pagination

Time to test your knowledge! Rank top expensive products, construct page 3 of customer directories, order employees across multiple columns, and explore shipment lag.

🏆Arena 03: Order & Windows

Key Takeaways: Sorting & Pagination

Three rules to keep from today.

📊 1. No Order Without ORDER BY

Rows have no built-in order. If the order matters (a top 10, a report, a page), write ORDER BY.

⚖️ 2. NULLs Top or Bottom

By default ASC puts NULLs last and DESC puts them first. On a DESC list people will read, write NULLS LAST.

🚀 3. Keyset Over Deep Offset

Deep OFFSET reads and throws away every skipped row. Start after the last row shown instead: WHERE customer_id > 1042 ORDER BY customer_id LIMIT 20. Same speed on any page.

💡 REMEMBER: LIMIT 10 alone gave a “biggest” order of ₹3,00,772. ORDER BY net_total DESC LIMIT 10 gives the real one, ₹11,11,861. First 10 is not top 10.

Interview Scenario: Pagination in Production

The question: “Users see the same product on page 1 and again on page 2. Why, and how do you fix it?”

⚠️ 1. Ties: Same Price, No Fixed Order

ORDER BY price DESC alone: two products at the same price have no fixed order. RetailMart has 13 shared prices, e.g. ₹750.98 (products 3284 and 3411). One can land on page 1 and again on page 2.

🛡️ 2. New Rows Shift OFFSET Pages

While the user reads page 1, a new row arrives at the top. Every row moves down one place, so OFFSET 20 now starts one row earlier: the last row of page 1 shows again on page 2.

🔄 3. The Fix: Tie-Breaker + Keyset

End the sort with a unique column: ORDER BY price DESC, product_id. For long or live lists, start after the last row shown (WHERE customer_id > 1042) instead of counting with OFFSET.

MODEL INTERVIEW ANSWER:

“Two causes: ties and moving data. I end every ORDER BY with a unique column so each row has one place, and for deep or live lists I page from the last key shown instead of using OFFSET.”

Execution Engine: The 6-Step PostgreSQL Lifecycle

You write SELECT first; PostgreSQL runs FROM first. Click each step.

⚙️ STEP-BY-STEP ENGINE PIPELINE:
QUERY: SELECT DISTINCT product_name, price * 1.18 AS price_with_gst FROM products.products WHERE price > 50000 ORDER BY price_with_gst DESC LIMIT 10;
STEP 01
FROM
Open the table: products.products
STEP 02
WHERE
Keep rows with price > 50000 (no aliases yet)
STEP 03
SELECT
Work out price * 1.18, name it price_with_gst
STEP 04
DISTINCT
Remove repeated rows
STEP 05
ORDER BY
Sort by price_with_gst (the alias works here)
STEP 06
LIMIT
Keep the first 10 rows
Standby • Step through the pipeline to see why aliases fail in WHERE but work in ORDER BY. (GROUP BY and HAVING, Module 2, run between WHERE and SELECT.) LIFECYCLE READY

Definition: The True SQL Execution Order

You write SELECT first, but PostgreSQL runs it after WHERE

📝 What You Write

SELECT → FROM → WHERE → ORDER BY → LIMIT: readable, but not the order things happen.

⚙️ What PostgreSQL Runs

FROM → WHERE → SELECT → DISTINCT → ORDER BY → LIMIT. Columns made in SELECT do not exist yet in WHERE.

💡 The Alias Mystery

An alias fails in WHERE (it runs before SELECT) and works in ORDER BY (it runs after).

An alias in WHERE vs ORDER BY
PostgreSQL
TRAPAlias in WHERE ▶ Run
-- ERROR on purpose: WHERE runs before SELECT
SELECT product_name,
       price * 1.18 AS price_with_gst
FROM products.products
WHERE price_with_gst > 50000
LIMIT 10;
→ ERROR: column "price_with_gst" does not exist
FIXRepeat the expression ▶ Run
SELECT product_name,
       price * 1.18 AS price_with_gst
FROM products.products
WHERE price * 1.18 > 50000
LIMIT 10;
→ 677 products cost over ₹50,000 with GST
WORKSAlias in ORDER BY ▶ Run
SELECT product_name,
       price * 1.18 AS price_with_gst
FROM products.products
ORDER BY price_with_gst DESC
LIMIT 10;
→ Top: HP Deserunt X, ₹1,76,503.14 with GST

Common Mistakes: 6 Sorting & Paging Traps

Only an error would warn you, and none of these gives one. Each just returns the wrong rows.

✖ 1. LIMIT 10 Is Not the Top 10

SELECT order_id, net_total FROM sales.orders LIMIT 10: the biggest of those 10 is ₹3,00,772. The real biggest order is ₹11,11,861.

✓ Fix: Sort first: ORDER BY net_total DESC LIMIT 10.
✖ 2. DESC Puts the Blanks on Top

ORDER BY delivered_date DESC LIMIT 10 returns 10 empty dates: all 14,982 blank rows come before any real date.

✓ Fix: Write ORDER BY delivered_date DESC NULLS LAST.
✖ 3. DESC Covers One Column Only

ORDER BY brand_id, price DESC still starts with brand 1. DESC belongs to price alone.

✓ Fix: Want both descending? ORDER BY brand_id DESC, price DESC starts with brand 117.
✖ 4. OFFSET Counts Rows, Not Pages

For page 3, LIMIT 20 OFFSET 3 returns customers 4 to 23.

✓ Fix: OFFSET = (page − 1) × 20, so page 3 is OFFSET 40.
✖ 5. No Tie-Breaker

₹750.98 is the price of two products (3284 and 3411). Sorted only by price, either can come first, on any page.

✓ Fix: End with a unique column: ORDER BY price DESC, product_id.
✖ 6. OFFSET Without ORDER BY

LIMIT 20 OFFSET 20 with no ORDER BY cuts page 2 out of an order that does not exist: repeats and gaps.

✓ Fix: Always ORDER BY a unique column before paging.

Real World: RetailMart Exploration Queries

How you look at a new table on day one: filter it, sort it, keep it small.

👥 1. Customers (50,000 Rows)

Which customers have their default address in Mumbai, Delhi or Bengaluru? 2,213 do. Sort by customer_id and look at 10 first (query below). (Adding the loyalty tier from customers.customers needs a JOIN: Module 2.)

🏪 2. Stores, Biggest First

Session 7’s 5 stores in Noida or Prayagraj over 3,000 sq ft, now with ORDER BY square_ft DESC: store 15 in Prayagraj (4,858 sq ft) comes first.

📦 3. Parcels With No Delivery Date

ORDER BY delivered_date NULLS FIRST puts the 14,982 blank dates on top, so the missing ones are the first thing you see.

Exploratory Production Query on Metro Customer Addresses
▶ Run Query
PostgreSQL
SQL DQLExploratory Production Query on Metro Customer Addresses
SELECT customer_id, city, state, pincode FROM customers.addresses WHERE is_default = TRUE AND city IN ('Mumbai', 'Delhi', 'Bengaluru') ORDER BY customer_id ASC LIMIT 10;

Interview Studio: Sorting, Pagination & Execution Order

Questions interviewers really ask about today’s topic, with the answer and the trap.

TOP INTERVIEW QUESTIONS
RESULT ORDER • GUARANTEES

“A query without ORDER BY returned rows sorted by id. Can you rely on that?”

✓ MODEL ANSWER:
No. Only ORDER BY promises an order. PostgreSQL returns rows in whatever order is cheapest for it right now: the order they sit on disk, the order an index gives, or several parallel workers mixing their results. After an UPDATE, a VACUUM or a different plan, the same query can come back in another order.
🧠 WHY INTERVIEWERS ASK THIS:

Many production bugs come from code that silently assumed an order. Interviewers check that you never do.

⚠️ COMMON TRAP TO AVOID:

“It always came back sorted when I tested it.” Small test tables hide this; big real tables expose it.

Sorting & Pagination Self-Check

Close your notes. Answer from memory. Click any card to reveal the verified answer.

1. Without ORDER BY, in what order do rows come back?
No guaranteed order. Whatever is cheapest for PostgreSQL at that moment. Only ORDER BY promises an order.
2. Where does ORDER BY price DESC put rows whose price is NULL?
First. PostgreSQL treats NULL as larger than any value. Write ORDER BY price DESC NULLS LAST to move them to the end.
3. In ORDER BY brand_id, price DESC, which column decides first?
brand_id (ascending, the default). price DESC only orders rows inside the same brand. DESC applies to price alone.
4. Which rows does LIMIT 20 OFFSET 40 return?
Rows 41 to 60 of the sorted result: page 3 at 20 rows per page. Without ORDER BY, “rows 41 to 60” means nothing.
5. Why does OFFSET 100000 get slow?
PostgreSQL cannot jump to row 1,00,001. It reads and discards 1,00,000 rows first. Keyset pagination (WHERE id > last_id) avoids that.
6. Why does WHERE price_with_gst > 50000 fail when price_with_gst is an alias?
WHERE runs before SELECT, and SELECT is where the alias is made. In ORDER BY it works, because sorting runs after SELECT.

The 6 Golden Laws Bento

The rules to remember from today, for every sorted list and every page you build.

📊 LAW 01 • ORDER

No Order Without ORDER BY

Rows have no built-in order. If the result must be sorted (a report, a top-10, a page), write ORDER BY, every time.

⚡ Tested once is not guaranteed.
🔗 LAW 02 • TIE-BREAKER

End Every Sort with a Unique Column

ORDER BY price DESC, product_id: when two prices are equal, the id decides. Each row gets exactly one place, so pages never overlap.

✔️ Unique last column = stable pages.
⚖️ LAW 03 • NULLS

Decide Where NULLs Go

ASC puts NULLs last, DESC puts them first. Say what you want with NULLS FIRST or NULLS LAST.

👀 A top-10 should start with real values.
🚀 LAW 04 • SCALE PAGINATION

Keyset Over Deep OFFSET

Deep OFFSET reads and discards every skipped row. For long lists, page with WHERE id > last_id ORDER BY id LIMIT 20.

⏱️ Keyset stays equally fast at any page depth.
⚙️ LAW 05 • EXECUTION ORDER

FROM → WHERE → SELECT → DISTINCT → ORDER BY → LIMIT

WHERE runs before SELECT makes the aliases, so an alias fails in WHERE and works in ORDER BY.

🧭 Know the run order and the errors make sense.
🛡️ LAW 06 • EXPLORING SAFELY

LIMIT While You Explore

Add LIMIT 10 while you look around a table: sales.orders alone has 1,50,000 rows. And never use OFFSET without ORDER BY.

💡 Small answers first, big ones on purpose.

Sorting & Pagination Quick Reference Studio

Every pattern from today as a real RetailMart query: run it, copy it, change it.

Sorting & Pagination Cheat Sheet
SQL REFERENCE
-- SORTING & NULLS
-- Top 10 most expensive▶ Run
SELECT product_id, product_name, price
FROM products.products
ORDER BY price DESC, product_id LIMIT 10;
-- Two columns: brand, then price▶ Run
SELECT product_name, brand_id, price FROM products.products
ORDER BY brand_id ASC, price DESC NULLS LAST LIMIT 10;
-- Missing values first: NULLS FIRST▶ Run
SELECT shipment_id, delivered_date
FROM sales.shipments
ORDER BY delivered_date NULLS FIRST LIMIT 10;
-- Alias in ORDER BY (works)▶ Run
SELECT product_name, price * 1.18 AS price_with_gst
FROM products.products
ORDER BY price_with_gst DESC LIMIT 10;
-- FILTER, SORT, PAGE
-- Filter, then sort, then cut▶ Run
SELECT order_id, order_date, net_total FROM sales.orders
WHERE order_status = 'Delivered'
ORDER BY net_total DESC LIMIT 5;
-- Page 3 with LIMIT + OFFSET▶ Run
SELECT customer_id, first_name FROM customers.customers
ORDER BY customer_id LIMIT 20 OFFSET 40;
-- Keyset: the page after id 1042▶ Run
SELECT customer_id, first_name FROM customers.customers
WHERE customer_id > 1042 ORDER BY customer_id LIMIT 20;
-- Stable pages: unique tie-breaker▶ Run
SELECT product_id, product_name, price
FROM products.products
ORDER BY price DESC, product_id
LIMIT 20 OFFSET 20;

Take-Home Challenge: Sorted Lists & Pages

Do these 4 drills on RetailMart in your own PostgreSQL before the next class.

🎯 THE 4-STAGE SORTING SPRINT:
Stage 1 — Top 10 and Bottom 10

From products.products, list the 10 most expensive and the 10 cheapest products. Add product_id as the tie-breaker in both.

Stage 2 — Stuck Parcels, Page 2

From sales.shipments, list the shipments with no delivered_date, oldest shipped_date first, 20 per page. Fetch page 2.

Stage 3 — Loss-Leader Audit

In products.products, fetch page 2 (rows 11–20) of all products priced under ₹200, sorted by price ascending with product_id as the tie-breaker.

Stage 4 — OFFSET vs Keyset

Get page 1 of customers.customers by customer_id, 20 per page. Then get page 2 twice: with OFFSET 20, and with WHERE customer_id > the last id of page 1. Both must return the same 20 rows.

Master Curriculum Journey Roadmap

🏁THESHIRAVERSE SQL PATH
✓ SESSION 01
What is SQL?
Relational Model • Port 5432 Server • 5 SQL Families
2 more
✓ SESSION 04
Data Types
NUMERIC vs FLOAT • INT / DATE / TIMESTAMPTZ • ALTER ... USING
3 more
★ SESSION 08 • YOU ARE HERE
Sorting & Pagination
ORDER BY • NULLS FIRST / LAST • LIMIT / OFFSET • Execution order
→
➜ NEXT • SESSION 09
String Functions
UPPER / LOWER / INITCAP • TRIM • SUBSTRING • CONCAT • REPLACE
49 more
🏁 SESSION 59 • FINISH LINE
SQL for BI Tools & Dashboards
Module 6 • Analyst Patterns • the finish line

59 sessions, from zero to job-ready. Today is session 8.

Next Session: String Functions

NEXT MISSION • MODULE 2 • SESSION 09
String Functions: Cleaning and Shaping Text

Names typed in capitals, extra spaces, phone numbers with +91 in front: real text is messy. Next class cleans it with SQL.

✂️
1. Case & Spaces

UPPER, LOWER, INITCAP and TRIM for names and emails typed every possible way.

🔎
2. Cutting Text

SUBSTRING, LEFT, RIGHT and POSITION to pull a domain out of an email or a code out of an id.

🔗
3. Joining Text

CONCAT, || and REPLACE to build full names and clean phone numbers.

Live class schedule & notes: String Functions →
NEXT UP • MODULE 2 • SESSION 09
→ next   ← back   F fullscreen   H hide bars   N notes
1 / 24