SELECT shows you everything. That's the problem.
You're a freshly hired data analyst. First task from your manager: "Pull everyone from Delhi with a score above 75."
You've just learned SELECT. You run SELECT * FROM students; — five rows drop on screen. You read each one. You compare. You mentally cross out Priya (Mumbai), Raj (Pune), Karan (Chennai), Simran (score: 61). You find Arjun. Done.
Now imagine that table has 10,000 rows. Then 500,000. Then 10 million. The manual-scan approach collapses at row 21.
Here's the same query — without WHERE and with WHERE. Click any condition below to see which rows pass:
Real tables don't have 5 rows.
Every production database you'll encounter in a job has tables with thousands, millions, even billions of rows. No analyst, no application, no dashboard ever reads all of them. Every real SQL query filters.
Think about what every real question sounds like:
-- "Show me customers from Mumbai" WHERE city = 'Mumbai' -- "Sales above ₹50,000 this quarter" WHERE amount > 50000 -- "Users who haven't logged in for 30 days" WHERE last_login < '2024-01-01' -- "Products that are out of stock" WHERE stock = 0 OR stock IS NULL
A WHERE query has exactly 5 moving parts.
Understanding the structure prevents 80% of syntax errors. Here's the full query broken into its components:
Now zoom into the WHERE condition itself — every condition has exactly 3 parts:
WHERE > 70 is broken — missing the column. WHERE score is broken — missing the operator and value.
One more thing beginners get wrong: SQL evaluates clauses in this order — not the order you write them:
SELECT name FROM students WHERE score > 80 is valid — score is used in WHERE but doesn't appear in the result. WHERE runs before SELECT, so the column just needs to exist in the table.
14 operators. You'll use 6 constantly.
LIKE patterns — the two wildcards you need to know:
| Pattern | What it matches | Example match |
|---|---|---|
| 'A%' | Starts with A, anything after | Arjun ✓ |
| '%n' | Ends with n, anything before | Arjun, Simran, Karan ✓ |
| '%ar%' | Contains 'ar' anywhere (case-insensitive) | Arjun, Karan ✓ |
| '_____' | Exactly 5 characters | Arjun, Priya, Karan ✓ |
'%n' → anything before, ends with n
'%ar%' → contains "ar" anywhere
'_____' → exactly 5 characters
'S___n' → 5 chars, starts S, ends n
Pattern visualiser — see which students match each pattern:
The NULL trap — this catches everyone at least once:
AND vs OR — same two conditions, completely different row counts:
SQL evaluates AND before OR, the same way multiplication runs before addition in math. Without parentheses, this causes silent logic errors:
You just joined ShopNest — India's newest e-commerce startup — as their first data analyst. The product catalog has 10,000 SKUs and zero consistency. Product names were entered by 12 different vendors. SKUs follow a loose pattern. Nobody agreed on anything.
Monday morning, your manager Rahul drops a sticky note on your desk with 6 tasks. "Need this filtered by noon. Brand partnership meeting." He's already walking away.
You open the products table. You try LIKE. It starts breaking immediately.
% and _. REGEXP gives you a language. And today, you need a language.The table you're working with — 10 rows in dev, 10,000 in prod:
| id | name | sku | category | price |
|---|---|---|---|---|
| 1 | iPhone 14 Pro | APL-IP14P-256 | Electronics | 89,999 |
| 2 | Samsung Galaxy S23 | SAM-GS23-128 | Electronics | 74,999 |
| 3 | Nike Air Max 90 | NIK-AM90-BLK | Footwear | 8,999 |
| 4 | Adidas Ultraboost 22 | ADI-UB22-WHT | Footwear | 12,999 |
| 5 | MacBook Pro 14 | APL-MBP14-512 | Electronics | 1,49,999 |
| 6 | HP Pavilion 15 | HP-PAV15-8GB | Electronics | 55,999 |
| 7 | Levi 511 Slim Jeans | LEV-511-BLU | Clothing | 3,999 |
| 8 | Allen Solly Formal Shirt | ALS-FSH-WHT | Clothing | 1,999 |
| 9 | OnePlus 11R 5G | OPL-11R-128 | Electronics | 39,999 |
| 10 | Reebok Classic Leather | RBK-CLS-LTH | Footwear | 5,999 |
You think: easy. You try LIKE with 'Apple'. Zero rows. Because no product name says "Apple" — they say "iPhone" and "MacBook". But the SKU always starts with APL for Apple. REGEXP to the rescue.
^APL means: SKU must START with APL. Any new Apple product with APL prefix is automatically included.^, REGEXP 'APL' would also match strings like "NAPL" or "SNAPL" anywhere in the value.SELECT name, sku FROM products WHERE sku REGEXP '^APL';
A "version number" means: there's at least one digit in the product name. LIKE can't express "contains any digit". REGEXP handles it in 3 characters.
OR name LIKE '%1%'
-- ... 8 more lines for 2–9
[0-9] is a character class. It matches any single digit. If the name contains even one digit anywhere, this row passes.[a-z] = any lowercase letter, [A-Za-z] = any letter at all.SELECT name, sku FROM products WHERE name REGEXP '[0-9]';
Two separate LIKE conditions would work — but REGEXP alternation | is cleaner and scales to 10 brands just as easily as 2.
OR sku LIKE 'SAM%'
(APL|SAM) means: match APL or SAM at the start. Add OnePlus: ^(APL|SAM|OPL). One expression, one edit.| symbol means OR inside a pattern. Wrap options in () to group them. You can add as many alternatives as you need: (APL|SAM|OPL|NIK).SELECT name, sku, price FROM products WHERE sku REGEXP '^(APL|SAM)';
The category column is unreliable. But the SKU prefix is consistent — NIK, ADI, RBK. REGEXP handles all three in one shot.
SELECT name, sku, price FROM products WHERE sku REGEXP '^(NIK|ADI|RBK)';
You need to match SKUs that do not start with exactly 3 uppercase letters. Use [A-Z]{3}- to define the correct format, then find rows that don't match it.
___ (3 underscores) means any 3 chars — but includes digits and lowercase too. LIKE can count positions but can't say "only uppercase letters".[A-Z]{3} means exactly 3 uppercase letters. NOT REGEXP flips it — returns SKUs that DON'T match the standard format.[A-Z] matches any uppercase letter. {3} means exactly 3 of the preceding element must appear. Together: "exactly 3 uppercase letters in a row." Use {2,4} for a range of 2 to 4.-- Find SKUs that don't follow the 3-letter prefix standard SELECT name, sku FROM products WHERE sku NOT REGEXP '^[A-Z]{3}-';
Two search terms, one column — alternation with no anchor. You don't need ^ here because "Pro" and "5G" can appear anywhere in the name.
OR name LIKE '%5G%'
^ or $ — the pattern can match anywhere in the string. Add tiers: '5G|Pro|Ultra|Max'.^, the pattern searches anywhere inside the string — same idea as LIKE '%Pro%' but you can chain alternatives: '5G|Pro|Ultra|Max' without adding more OR lines.SELECT name, sku, price FROM products WHERE name REGEXP '5G|Pro';
All 6 tasks done. Here's every pattern you used — and the full reference:
^APL = must start with APL.n$ = must end with n.{} for exact lengths.[0-9] = any digit. Used in Task 2 & 5.| for multi-option alternation.MySQL / MariaDB:
WHERE sku REGEXP '^APL' — native, no setup.SQLite (this playground): REGEXP is enabled here via a registered JavaScript function.
PostgreSQL: uses
~ (case-sensitive) or ~* (case-insensitive) — WHERE sku ~ '^APL'.SQL Server: no native REGEXP. Use
LIKE for simple patterns, or PATINDEX with limited wildcards.
Ten examples. Each one adds something new.
All examples use the same students table you already know: Arjun (Delhi/88), Priya (Mumbai/72), Raj (Pune/95), Simran (Delhi/61), Karan (Chennai/80).
The most common WHERE clause. Text values must be in single quotes. When using =, comparisons are case-sensitive — 'Delhi' and 'delhi' are different values. (Note: LIKE is case-insensitive for plain letters in SQLite.)
SELECT * FROM students WHERE city = 'Delhi';
| id | name | city | score |
|---|---|---|---|
| 1 | Arjun | Delhi | 88 |
| 4 | Simran | Delhi | 61 |
Numbers don't need quotes. > means strictly greater than — Karan (80) does not qualify for score > 80. Use >= to include 80.
SELECT name, score FROM students WHERE score > 80;
| name | score |
|---|---|
| Arjun | 88 |
| Raj | 95 |
AND tightens the filter. Simran is from Delhi but her score (61) fails the second condition — AND excludes her. Only Arjun passes both.
SELECT name, city, score FROM students WHERE city = 'Delhi' AND score > 70;
| name | city | score |
|---|---|---|
| Arjun | Delhi | 88 |
IN is cleaner than writing multiple OR conditions. Always wrap the list in parentheses. Equivalent to city = 'Delhi' OR city = 'Mumbai'.
SELECT name, city FROM students WHERE city IN ('Delhi', 'Mumbai');
| name | city |
|---|---|
| Arjun | Delhi |
| Priya | Mumbai |
| Simran | Delhi |
% is a wildcard meaning "zero or more characters." 'A%' means "starts with A, then anything." Only Arjun qualifies here.
SELECT name FROM students WHERE name LIKE 'A%';
| name |
|---|
| Arjun |
BETWEEN low AND high includes both endpoints. It's equivalent to score >= 60 AND score <= 85. Raj (95) is above 85 and excluded.
SELECT name, score FROM students WHERE score BETWEEN 60 AND 85;
| name | score |
|---|---|
| Priya | 72 |
| Simran | 61 |
| Karan | 80 |
Putting % at the start anchors the pattern to the end of the value. '%n' matches any name ending in the letter n. Three out of five students qualify — Arjun, Simran, and Karan all end with n.
SELECT name FROM students WHERE name LIKE '%n';
| name |
|---|
| Arjun |
| Simran |
| Karan |
Wrapping a substring with % on both sides matches it anywhere in the value. SQLite's LIKE is case-insensitive for ASCII letters — '%ar%' matches both Arjun (characters 1–2) and Karan (characters 2–3).
SELECT name FROM students WHERE name LIKE '%ar%';
| name |
|---|
| Arjun |
| Karan |
Prefix LIKE with NOT to invert the match — return rows where the pattern does not apply. This query excludes any student from a city starting with 'D' (Delhi), so only Mumbai, Pune, and Chennai students appear.
SELECT name, city FROM students WHERE city NOT LIKE 'D%';
| name | city |
|---|---|
| Priya | Mumbai |
| Raj | Pune |
| Karan | Chennai |
Mixing AND and OR requires parentheses to make intent explicit. Without them, AND runs first (higher precedence). This query correctly finds Delhi or Mumbai students who also scored above 70 — Simran (Delhi, 61) is excluded because 61 fails the score condition.
SELECT name, city, score FROM students WHERE (city = 'Delhi' OR city = 'Mumbai') AND score > 70;
| name | city | score |
|---|---|---|
| Arjun | Delhi | 88 |
| Priya | Mumbai | 72 |
Nine traps — every beginner hits at least two.
NULL = NULL as NULL (not TRUE), so nothing matches. Always use IS NULL or IS NOT NULL.WHERE city = Delhi throws an error because SQL thinks Delhi is a column name. Always wrap text values in single quotes: WHERE city = 'Delhi'. Numbers need no quotes: WHERE score = 80.WHERE score == 80 is invalid SQL. SQL equality is single =. Double == is Python and JavaScript. This one bites almost every developer switching to SQL for the first time.BETWEEN 70 TO 90 is a syntax error. Think of it as: "score is between 70 AND 90." Always: BETWEEN low AND high.WHERE city = 'Delhi' AND = 'Mumbai' is broken. SQL doesn't inherit the column from the previous condition. Write it in full: WHERE city = 'Delhi' OR city = 'Mumbai'. Or cleaner: WHERE city IN ('Delhi','Mumbai').WHERE city NOT IN ('Delhi', NULL) returns 0 rows with no error. SQL can never confirm that any value is "not equal to NULL" — the comparison resolves to NULL (unknown), which evaluates as FALSE. If NULLs may exist, filter them separately with IS NOT NULL.WHERE city='Delhi' OR city='Mumbai' AND score>70 is not what it looks like. SQL runs the AND first: it becomes Delhi OR (Mumbai AND score>70). Simran from Delhi (score 61) still passes. Fix: add parentheses — WHERE (city='Delhi' OR city='Mumbai') AND score>70.WHERE name LIKE 'arjun' returns Arjun in SQLite, but WHERE name = 'arjun' returns nothing (case-sensitive). Use = for exact case-sensitive matching, and LIKE when you want case-insensitive lookup or need wildcard patterns.What you just learned.
- WHERE goes after FROM and filters rows. Only rows where the condition is TRUE appear in the result.
- Every condition has 3 parts: column — operator — value. If one is missing, that's your bug.
-
Text values need single quotes (
'Delhi'). Numbers do not (80). Forgetting quotes is the #1 beginner error. -
Never use
= NULL. It silently returns 0 rows. UseIS NULLorIS NOT NULL. - AND = both conditions required (stricter, fewer rows). OR = either condition sufficient (more permissive, more rows).
-
BETWEEN includes both endpoints.
BETWEEN 70 AND 90is the same as>= 70 AND <= 90.WHERE score BETWEEN 70 AND 90 -
IN is cleaner than multiple ORs.
city IN ('Delhi','Mumbai')is easier to read thancity = 'Delhi' OR city = 'Mumbai'. - WHERE never changes data. Like SELECT, it only reads. Safe to run anywhere, any number of times.
-
REGEXP is LIKE's powerful sibling. Use
^for start,$for end,[AK]for character classes,.for any char. Available in MySQL, SQLite; PostgreSQL uses~; SQL Server uses LIKE. -
NOT BETWEEN and NOT LIKE invert their operators exactly.
score NOT BETWEEN 70 AND 90returns scores below 70 or above 90.
50 WHERE Questions
Work in order — each question builds on the last. Q01–Q35 use the students table · Q36–Q45 use the Employees table · Q46–Q50 are REGEXP challenges.
-- Q01–Q35 | students table DROP TABLE IF EXISTS students; CREATE TABLE students ( id INT, name VARCHAR(50), city VARCHAR(50), score INT ); INSERT INTO students VALUES (1, 'Arjun', 'Delhi', 88), (2, 'Priya', 'Mumbai', 72), (3, 'Raj', 'Pune', 95), (4, 'Simran', 'Delhi', 61), (5, 'Karan', 'Chennai', 80);
-- Q36–Q45 | Employees table (20 rows) DROP TABLE IF EXISTS Employees; CREATE TABLE Employees ( EmployeeID INT PRIMARY KEY, FirstName VARCHAR(50), LastName VARCHAR(50), Department VARCHAR(50), JobTitle VARCHAR(100), Salary DECIMAL(10,2), HireDate DATE, City VARCHAR(50), IsActive BIT, ManagerID INT NULL ); INSERT INTO Employees VALUES (1, 'Aarav', 'Sharma', 'IT', 'SQL Developer', 72000, '2019-03-15', 'Mumbai', 1, NULL), (2, 'Priya', 'Mehta', 'HR', 'HR Manager', 65000, '2018-07-01', 'Delhi', 1, NULL), (3, 'Rohan', 'Verma', 'Sales', 'Sales Executive', 45000, '2021-06-10', 'Pune', 1, 1), (4, 'Sneha', 'Kapoor', 'IT', 'Data Analyst', 68000, '2020-01-20', 'Bangalore', 1, 1), (5, 'Karan', 'Singh', 'Finance', 'Financial Analyst', 75000, '2017-11-05', 'Mumbai', 1, NULL), (6, 'Anjali', 'Patel', 'Marketing', 'Marketing Executive', 42000, '2022-03-01', 'Ahmedabad', 1, 2), (7, 'Vikram', 'Nair', 'IT', 'Python Developer', 80000, '2019-08-22', 'Bangalore', 1, 1), (8, 'Pooja', 'Joshi', 'Sales', 'Sales Manager', 70000, '2016-05-18', 'Delhi', 1, NULL), (9, 'Arjun', 'Reddy', 'Finance', 'Senior Accountant', 60000, '2020-09-30', 'Hyderabad', 0, 5), (10, 'Meera', 'Iyer', 'HR', 'HR Executive', 38000, '2023-01-15', 'Chennai', 1, 2), (11, 'Rahul', 'Gupta', 'IT', 'BI Developer', 77000, '2018-04-10', 'Mumbai', 1, 1), (12, 'Divya', 'Shah', 'Marketing', 'Content Strategist', 48000, '2021-11-20', 'Pune', 1, 6), (13, 'Nikhil', 'Desai', 'Sales', 'Sales Executive', 43000, '2022-07-05', 'Surat', 0, 8), (14, 'Kavya', 'Pillai', 'IT', 'Cloud Engineer', 85000, '2017-02-28', 'Bangalore', 1, NULL), (15, 'Aditya', 'Bose', 'Finance', 'Finance Manager', 90000, '2015-10-01', 'Kolkata', 1, NULL), (16, 'Simran', 'Kaur', 'HR', 'Recruiter', 35000, '2023-06-01', 'Delhi', 1, 2), (17, 'Manish', 'Tiwari', 'Marketing', 'SEO Analyst', 40000, '2022-09-14', 'Lucknow', 1, 6), (18, 'Ritika', 'Saxena', 'Sales', 'Business Dev Exec', 55000, '2020-04-17', 'Mumbai', 0, 8), (19, 'Siddharth', 'Rao', 'IT', 'Database Admin', NULL, '2021-08-03', 'Hyderabad', 1, 1), (20, 'Nisha', 'Malhotra', 'Finance', 'Junior Accountant', 32000, '2023-03-20', 'Delhi', 1, 15);