Imagine your college keeps one giant register of all 5,000 students. The placement cell walks in: "Give me the names of final-year CS students with CGPA above 8." What do you do? You go through the register, keep only the rows that match, and write down just the columns you need. That thought process — filter rows, pick columns — is SQL. This whole lesson is just teaching your brain to write that thought down formally.
SELECT and FROM — asking the register a question
Every read query has the same skeleton: FROM names the table, SELECT names the columns you want back. For the examples in this lesson, we'll use this employees table — nine people, three departments, one missing salary (that blank will become important very soon):
| emp_id | name | department | salary |
|---|---|---|---|
| 1 | Alice Sharma | Engineering | 95000 |
| 2 | Bob Iyer | Engineering | 72000 |
| 3 | Chitra Rao | Sales | 65000 |
| 4 | David Paul | Sales | 58000 |
| 5 | Esha Nair | Engineering | 88000 |
| 6 | Farhan Khan | Marketing | 60000 |
| 7 | Gita Menon | Marketing | 60000 |
| 8 | Harish Verma | Sales | NULL |
| 9 | Amit Verma | Sales | 52000 |
The simplest possible query grabs everything:
SELECT * FROM employees;* means "all columns". Handy for exploring, but in real code you name the columns you need — it's faster, and your query doesn't break when someone adds a column later:
SELECT name, salary
FROM employees;Result
Alice Sharma | 95000 Bob Iyer | 72000 ... (9 rows — every employee, just two columns)
You can even compute new columns on the fly and name them with AS:
SELECT name, salary * 12 AS annual_salary
FROM employees;WHERE — keeping only the rows you care about
WHERE is the bouncer at the door: every row walks up, the condition checks it, and only rows where the condition is TRUE get in. Conditions use operators you already know: =, <> (not equal), >, <, >=, <= — combined with AND, OR, NOT, plus two nice shortcuts: BETWEEN and IN.
SELECT name, department, salary
FROM employees
WHERE department = 'Sales' AND salary > 55000;Result
Chitra Rao | Sales | 65000 David Paul | Sales | 58000
The shortcuts read even more like English — WHERE salary BETWEEN 60000 AND 90000 (inclusive on both ends, a detail interviewers like), and WHERE department IN ('Sales', 'Marketing') instead of chaining ORs.
DISTINCT — each value only once
"Which departments exist in this company?" A plain SELECT department would print Sales four times. DISTINCT collapses the duplicates:
SELECT DISTINCT department
FROM employees;Result
Engineering Sales Marketing
Relative order is not guaranteed — no ORDER BY, no promises.
One subtlety worth locking in now: with two columns, DISTINCT de-duplicates the pair as a whole — SELECT DISTINCT city, state keeps (Mumbai, Maharashtra) and (Mumbai, Goa) both, because the combinations differ. It never de-duplicates each column separately.
NULL — a blank is not a zero
Look back at Harish Verma's salary in our table. It's not 0. It's not empty text. It's NULL — meaning "unknown". Maybe HR hasn't entered it yet; maybe it doesn't apply. SQL is very philosophical about this: you cannot ask "is something equal to unknown?" and get a yes.
So what do you think this returns?
SELECT name FROM employees WHERE salary = NULL;Result
(zero rows — always, even though Harish's salary IS null)
Comparing anything with NULL using = gives UNKNOWN, and WHERE only keeps rows that are confidently TRUE.
NULL gets its own special checks:
-- the right way
SELECT name FROM employees WHERE salary IS NULL;
SELECT name FROM employees WHERE salary IS NOT NULL;Selection-round radar: the = NULL trap is asked practically everywhere — service and product companies both. If you're ever shown a query containing = NULL or != NULL, the bug is the question. Say "three-valued logic" in your answer and watch the interviewer's eyebrows go up.
LIKE — searching when you only know a part
You type "Ra" in your phone's contact search and get Rahul, Ravi, Ramesh. SQL's version is LIKE with two wildcards: % matches any number of characters (including zero), and _ matches exactly one.
SELECT name FROM employees WHERE name LIKE 'A%'; -- starts with A
SELECT name FROM employees WHERE name LIKE '%Verma'; -- ends with Verma
SELECT name FROM employees WHERE name LIKE '_a%'; -- second letter is aResult
'A%' → Alice Sharma, Amit Verma '%Verma' → Harish Verma, Amit Verma '_a%' → David Paul, Farhan Khan, Harish Verma
Need to search for a literal % inside text (say, a discount column storing "50% off")? Escape it: WHERE offer LIKE '%50\%%' — the \% is a real percent sign, the outer %s are wildcards.
ORDER BY — sorting the answer
Without ORDER BY, SQL promises you nothing about row order — whatever order you see is an accident of storage. So when order matters, say so:
SELECT name, department, salary
FROM employees
ORDER BY salary DESC, name ASC;Two sort keys: highest salary first, and if two people tie, their names break the tie alphabetically. Multi-key sorting like this is how you make an ordering stable — remember that word, it returns in the pagination story below. One MySQL quirk to know: MySQL treats NULL as lower than every value — so ASC puts NULL salaries first and DESC puts them last. To force NULLs to the end regardless of direction: ORDER BY salary IS NULL, salary ASC.
LIMIT and OFFSET — because nobody loads 10 lakh rows
When Instagram opens your feed, it doesn't fetch every post ever made — it fetches one small page, then the next when you scroll. SQL pages results the same way:
-- top 3 earners (skipping NULL salaries)
SELECT name, salary
FROM employees
WHERE salary IS NOT NULL
ORDER BY salary DESC
LIMIT 3;
-- "page 2": skip 3 rows, take the next 3
SELECT name, salary
FROM employees
WHERE salary IS NOT NULL
ORDER BY salary DESC
LIMIT 3 OFFSET 3;Result
Page 1 → Alice Sharma (95000), Esha Nair (88000), Bob Iyer (72000) Page 2 → Chitra Rao (65000), then the two 60000s…
SQL Server spells this differently (SELECT TOP 3 ...), and MySQL also accepts the short form LIMIT 3, 3 — which means "skip 3, take 3", not rows 3 to 3. That comma form confuses everyone at least once; prefer the explicit LIMIT ... OFFSET ....
But here's the hidden problem. Farhan and Gita both earn exactly 60000. If that tie sits on a page boundary and your ORDER BY has no tie-breaker, the database may order them differently on different runs — the same person can appear on two pages, or on none. The fix is the stable ordering you just learned: ORDER BY salary DESC, emp_id ASC. You'll meet this exact scenario in the Practice Zone.
The order SQL actually runs your query
Here's the "wait, what?" moment of this lesson. You write SELECT first — but SQL runs it almost last.
Why should you care? Try this innocent-looking query:
SELECT salary * 12 AS annual
FROM employees
WHERE annual > 1000000; -- ERROR: unknown column 'annual'It fails. When WHERE runs (step 2), the alias annual hasn't been created yet — that happens in SELECT (step 5). But ORDER BY annual works fine, because sorting happens after SELECT. One diagram, and a whole family of "why doesn't this work" questions becomes obvious. This same diagram will explain WHERE vs HAVING in lesson 3.
Common mistakes (I've made all of these)
- Writing
WHERE salary = NULLand wondering why the result is empty — useIS NULL. - Using a
SELECTalias insideWHERE— it doesn't exist yet; repeat the expression or use a subquery. - Assuming rows come back in insertion order without
ORDER BY— they don't, and it will bite you exactly once in production. - Paginating on a non-unique sort key — add a tie-breaker column.
SELECT *in application code — name your columns.
Quick recap
| Clause | Job | Remember |
|---|---|---|
SELECT / FROM | pick columns / name the table | name columns explicitly; AS for aliases |
WHERE | filter rows | keeps only rows that are TRUE — UNKNOWN is out |
DISTINCT | remove duplicates | de-duplicates the whole column combination |
IS NULL | check for missing values | never = NULL |
LIKE | pattern search | % = many chars, _ = exactly one |
ORDER BY | sort | multi-key for stable order; MySQL sorts NULL lowest |
LIMIT / OFFSET | pagination | always pair with a stable ORDER BY |
Practice Zone — PYQs from real selection rounds
Time to earn this lesson. Six click-to-answer questions and three write-the-query challenges — under each one you'll see the companies where that pattern has been asked. Attempt first, reveal second.
I run SELECT DISTINCT city, state FROM addresses;. What exactly gets de-duplicated?
Asked in


Some rows in my table genuinely have a NULL salary. What does SELECT * FROM employees WHERE salary = NULL; return?
Asked in


Which LIKE pattern matches every name whose second letter is 'a' (like 'Ravi' or 'Karan')?
Asked in


This query fails: SELECT salary * 12 AS annual FROM employees WHERE annual > 1000000;. Why can't WHERE see the alias annual?
Asked in


In MySQL, I want rows 11 to 20 (the second page of 10 results). Which query is right?
Asked in


In MySQL, I run SELECT name, salary FROM employees ORDER BY salary DESC; and one employee has a NULL salary. Where does that row appear?
Asked in


Now write real queries. Read the table, think, write your answer on paper (seriously — paper), then reveal:
Given the employees table below, write a query to list the distinct department names present in the table.
Asked in


employees9 rows
| emp_id | name | department | salary | manager_id | join_date | |
|---|---|---|---|---|---|---|
| 1 | Alice Sharma | Engineering | 95000 | NULL | 2019-03-12 | alice.sharma@corp.com |
| 2 | Bob Iyer | Engineering | 72000 | 1 | 2020-07-01 | bob.iyer@corp.com |
| 3 | Chitra Rao | Sales | 65000 | NULL | 2018-11-20 | chitra.rao@corp.com |
| 4 | David Paul | Sales | 58000 | 3 | 2021-01-15 | david.paul@gmail.com |
| 5 | Esha Nair | Engineering | 88000 | 1 | 2019-09-05 | esha.nair@corp.com |
| 6 | Farhan Khan | Marketing | 60000 | NULL | 2020-02-28 | farhan.khan@corp.com |
| 7 | Gita Menon | Marketing | 60000 | 6 | 2022-04-10 | gita.menon@gmail.com |
| 8 | Harish Verma | Sales | NULL | 3 | 2022-06-18 | harish.verma@corp.com |
| 9 | Amit Verma | Sales | 52000 | 3 | 2021-08-01 | amit.verma@corp.com |
Using the same employees table, write a query to return the 3 highest-paid employees, excluding anyone whose salary is NULL.
Asked in


employees9 rows
| emp_id | name | department | salary | manager_id | join_date | |
|---|---|---|---|---|---|---|
| 1 | Alice Sharma | Engineering | 95000 | NULL | 2019-03-12 | alice.sharma@corp.com |
| 2 | Bob Iyer | Engineering | 72000 | 1 | 2020-07-01 | bob.iyer@corp.com |
| 3 | Chitra Rao | Sales | 65000 | NULL | 2018-11-20 | chitra.rao@corp.com |
| 4 | David Paul | Sales | 58000 | 3 | 2021-01-15 | david.paul@gmail.com |
| 5 | Esha Nair | Engineering | 88000 | 1 | 2019-09-05 | esha.nair@corp.com |
| 6 | Farhan Khan | Marketing | 60000 | NULL | 2020-02-28 | farhan.khan@corp.com |
| 7 | Gita Menon | Marketing | 60000 | 6 | 2022-04-10 | gita.menon@gmail.com |
| 8 | Harish Verma | Sales | NULL | 3 | 2022-06-18 | harish.verma@corp.com |
| 9 | Amit Verma | Sales | 52000 | 3 | 2021-08-01 | amit.verma@corp.com |
A UI paginates the employees table 5 rows per page, ordered by salary descending, using LIMIT 5 OFFSET 0 for page 1 and LIMIT 5 OFFSET 5 for page 2 (excluding employees with a NULL salary). Write the query used for page 2, and think about what can go wrong at the page boundary given that Farhan Khan and Gita Menon both earn exactly 60000.
Asked in


employees9 rows
| emp_id | name | department | salary | manager_id | join_date | |
|---|---|---|---|---|---|---|
| 1 | Alice Sharma | Engineering | 95000 | NULL | 2019-03-12 | alice.sharma@corp.com |
| 2 | Bob Iyer | Engineering | 72000 | 1 | 2020-07-01 | bob.iyer@corp.com |
| 3 | Chitra Rao | Sales | 65000 | NULL | 2018-11-20 | chitra.rao@corp.com |
| 4 | David Paul | Sales | 58000 | 3 | 2021-01-15 | david.paul@gmail.com |
| 5 | Esha Nair | Engineering | 88000 | 1 | 2019-09-05 | esha.nair@corp.com |
| 6 | Farhan Khan | Marketing | 60000 | NULL | 2020-02-28 | farhan.khan@corp.com |
| 7 | Gita Menon | Marketing | 60000 | 6 | 2022-04-10 | gita.menon@gmail.com |
| 8 | Harish Verma | Sales | NULL | 3 | 2022-06-18 | harish.verma@corp.com |
| 9 | Amit Verma | Sales | 52000 | 3 | 2021-08-01 | amit.verma@corp.com |
FAQ
Is SQL case-sensitive?
Keywords are not — select and SELECT both work. The convention (and what this course uses) is UPPERCASE keywords, lowercase table/column names. String values may or may not be case-sensitive depending on the database's collation — in default MySQL, 'sales' = 'Sales' is true.
What is the difference between WHERE and HAVING?
WHERE filters individual rows before any grouping; HAVING filters groups after GROUP BY. The full story — with the diagram that makes it obvious — is in the GROUP BY lesson.
Why does my query return rows in a weird order?
Because you didn't ask for an order. Without ORDER BY, the database returns rows in whatever order is cheapest for it. If order matters, say so explicitly.
Next lesson: data almost never lives in one table — Lesson 2: SQL Joins →


