SELECT, WHERE & ORDER BY
In one line
SELECT picks the columns, WHERE keeps only the rows that pass a condition, and ORDER BY sorts what is left; without ORDER BY the database promises no order at all.
What it is
A SQL query describes the result you want, not the steps to get it. The simplest shape is SELECT columns FROM table WHERE condition ORDER BY column LIMIT n. The database decides how to fetch the rows; you only state which rows and columns you want and how they should be sorted.
WHERE filters individual rows before anything is grouped or returned. Conditions combine with AND, OR and NOT and use operators such as =, <>, <, BETWEEN, IN, LIKE and IS NULL. ORDER BY sorts the final result, ascending by default, and LIMIT (or the standard FETCH FIRST n ROWS ONLY) cuts it to the first n rows.
NULL means 'unknown', not zero or empty. Any comparison with NULL, even NULL = NULL, gives UNKNOWN, and WHERE only keeps rows where the condition is TRUE. That one rule explains many surprising results.
Why interviewers ask
Almost every SQL interview starts here, and small details separate candidates: knowing that WHERE runs before SELECT (so aliases are not visible in WHERE), that NULL needs IS NULL, and that row order is only guaranteed with ORDER BY. Getting these right shows you can write queries that are correct on real, messy data.
How it works
- 1The query is written as SELECT ... FROM ... WHERE ... ORDER BY ... LIMIT, but it is logically evaluated in a different order: FROM, then WHERE, then GROUP BY and HAVING, then SELECT, then DISTINCT, then ORDER BY, then LIMIT/OFFSET.
- 2FROM names the table (or joined tables) that supply the rows.
- 3WHERE tests each row. A row survives only if the condition is TRUE; FALSE and UNKNOWN (anything involving NULL) are both thrown away.
- 4SELECT computes the output columns, including expressions like salary * 12 and aliases like AS annual. Because this happens after WHERE, an alias cannot be used in WHERE, but it can be used in ORDER BY.
- 5ORDER BY sorts the result by one or more columns, each ASC or DESC. Ties are broken by the next column listed; if you list none, tied rows can come back in any order.
- 6LIMIT n keeps the first n rows of the sorted result; OFFSET k skips k rows first. LIMIT without ORDER BY returns an arbitrary n rows.
Worked example · Top 2 earners in Eng or Sales on a 5-row table
- Table employees(id, name, dept, salary): (1, Asha, Eng, 120000), (2, Ben, Sales, 70000), (3, Chen, Eng, 95000), (4, Dana, HR, NULL), (5, Eli, Sales, 82000).
- Query: SELECT name, salary FROM employees WHERE dept IN ('Eng', 'Sales') AND salary > 80000 ORDER BY salary DESC LIMIT 2.
- FROM gives all 5 rows. WHERE checks each: Asha passes (Eng, 120000). Ben fails (70000 is not > 80000). Chen passes (95000). Dana fails (HR, and NULL > 80000 is UNKNOWN anyway). Eli passes (82000).
- SELECT keeps two columns: (Asha, 120000), (Chen, 95000), (Eli, 82000).
- ORDER BY salary DESC sorts them: Asha 120000, Chen 95000, Eli 82000.
- LIMIT 2 keeps the first two: Asha and Chen. Note that Dana would also vanish from a query with WHERE salary <> 100000, because NULL <> 100000 is UNKNOWN, not TRUE.
Picture it
It is like asking a librarian for books: FROM is the shelf, WHERE is 'only books about cooking published after 2020', SELECT is 'just tell me the title and author', and ORDER BY is 'newest first'. If you do not say how to sort, the librarian hands them over in whatever order they happened to be picked up.
Reference code
-- employees(id, name, dept, salary)
SELECT name,
salary,
salary * 12 AS annual -- alias usable in ORDER BY, not in WHERE
FROM employees
WHERE dept IN ('Eng', 'Sales')
AND salary > 80000 -- NULL salaries are dropped here
ORDER BY salary DESC, id -- id breaks ties deterministically
LIMIT 2; -- standard SQL: FETCH FIRST 2 ROWS ONLY
-- Rows with no salary: never use = NULL
SELECT name FROM employees WHERE salary IS NULL;
-- Case-insensitive prefix search
SELECT name FROM employees WHERE name ILIKE 'a%'; -- PostgreSQL
-- MySQL: WHERE name LIKE 'a%' (default collation is case-insensitive)The first query returns Asha and Chen from the example table. salary * 12 AS annual is computed in SELECT, so it could be sorted on but not filtered on in WHERE. The second query is the only correct way to find missing salaries; WHERE salary = NULL returns zero rows.
Key patterns
NULL-safe filtering
When: A column can be NULL
How: Use IS NULL / IS NOT NULL, COALESCE(col, default) for defaults, or IS DISTINCT FROM for a NULL-safe 'not equal' (MySQL uses the <=> operator for NULL-safe equality).
Deterministic sorting
When: Paginating or showing 'top N'
How: Always ORDER BY a unique column last (often the primary key) so ties are broken the same way every time.
Keyset pagination
When: Deep paging through large tables
How: Remember the last sort key seen and filter WHERE key < last_key ORDER BY key DESC LIMIT n, instead of OFFSET, which still reads every skipped row.
Pattern matching
When: Searching text
How: LIKE 'abc%' matches a prefix, '%abc%' a substring. In PostgreSQL LIKE is case-sensitive and ILIKE is case-insensitive; in MySQL case sensitivity depends on the column collation, and the default collations are case-insensitive.
Real-world use
Admin dashboards
Back-office tools built on PostgreSQL or MySQL, such as a Django or Rails admin page, run queries like SELECT ... WHERE status = 'pending' ORDER BY created_at DESC LIMIT 50 to show the newest items needing attention.
Product listing pages
An e-commerce category page filters by category and price range in WHERE, sorts by price or popularity with ORDER BY, and pages through results with LIMIT; large sites switch to keyset pagination (WHERE id < last_seen_id) because a big OFFSET still reads and discards all skipped rows.
Analytics in warehouses
Analysts in BigQuery, Snowflake or Redshift write the same SELECT / WHERE / ORDER BY shape; filtering early in WHERE (for example on a date partition column) cuts how much data is scanned and therefore the bill.
Data cleaning checks
Engineers run queries like SELECT COUNT(*) FROM users WHERE email IS NULL to find missing data before a migration, precisely because = NULL would silently match nothing.
In a project
You are building the 'My orders' screen for a shopping app backed by PostgreSQL. A user can filter by status and see the 20 most recent orders, then load more as they scroll.
- Write SELECT id, total, status, created_at FROM orders WHERE user_id = $1 AND status = $2 ORDER BY created_at DESC, id DESC LIMIT 20, using bind parameters instead of string concatenation to prevent SQL injection.
- Add id as a tie-breaker in ORDER BY so two orders with the same timestamp always come back in the same order and no row is shown twice or skipped between pages.
- For 'load more', use keyset pagination: pass the last (created_at, id) seen and add WHERE (created_at, id) < ($3, $4) instead of a growing OFFSET.
- Handle optional filters explicitly (status IS NULL in the app means no status filter) rather than comparing with NULL in SQL.
Impact: The list loads in a few milliseconds even for users with thousands of orders, pages never duplicate or drop rows, and the query is safe against injection. Keyset pagination keeps page 50 as fast as page 1.
Trade-offs
Use it when
- WHERE for any row-level filter, as early as possible, so less data flows to later steps
- ORDER BY whenever the order of rows matters to the user or to pagination
- LIMIT to cap result size for lists, previews and 'top N' questions
Avoid it when
- SELECT * in application code: it fetches unused columns and breaks when columns are added or reordered
- Large OFFSET values for deep pagination: the database still reads and discards every skipped row
- Wrapping a filtered column in a function (WHERE YEAR(created_at) = 2026) when a range (created_at >= '2026-01-01' AND created_at < '2027-01-01') would let an index be used
Cost: Without a useful index, WHERE is a full scan, O(n). ORDER BY needs a sort, O(n log n), unless an index already provides the order. ORDER BY ... LIMIT k can use a top-k sort that only keeps k rows in memory.
Common mistakes
- Writing WHERE col = NULL or col <> NULL: both are UNKNOWN for every row, so nothing matches. Use IS NULL / IS NOT NULL.
- Assuming rows come back in insertion or primary key order without ORDER BY. The order is not guaranteed and can change after an update, a vacuum or a parallel plan.
- Using a SELECT alias in WHERE. WHERE is evaluated before SELECT, so PostgreSQL reports that the column does not exist.
- Forgetting operator precedence: AND binds tighter than OR, so WHERE a = 1 OR b = 2 AND c = 3 means a = 1 OR (b = 2 AND c = 3). Add parentheses.
- Forgetting that rows with NULL fail both WHERE x > 5 and WHERE NOT (x > 5), so a filter and its opposite do not always add up to the whole table.
Interview questions
Quick revision
- Logical order: FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY, LIMIT.
- WHERE keeps only TRUE rows; NULL comparisons are UNKNOWN and dropped.
- Use IS NULL / IS NOT NULL, never = NULL.
- No ORDER BY means no guaranteed order. Add a unique tie-breaker for stable pages.
- Aliases from SELECT work in ORDER BY, not in WHERE.
- AND binds tighter than OR: use parentheses.
- LIMIT n OFFSET k (PostgreSQL, MySQL); standard SQL is OFFSET k ROWS FETCH FIRST n ROWS ONLY.
- PostgreSQL LIKE is case-sensitive (ILIKE is not); MySQL depends on collation.
Check yourself
1. A 5-row employees table has salaries 120000, 70000, 95000, NULL and 82000. How many rows does WHERE salary <> 100000 return?
2. Why does SELECT salary * 12 AS annual FROM employees WHERE annual > 100000 fail in PostgreSQL?
3. Which condition correctly finds employees with no manager?
4. A query has no ORDER BY. In what order are rows returned?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.