SQL · Reference
Database & SQL
From foundations to advanced querying — the complete notes: DDL, DML, joins, subqueries, aggregation, and more.
Contents
- 1 What is a Database
- 2 RDBMS Properties
- 3 Types of Keys
- 4 DDL Commands
- 5 DML Commands
- 6 Querying Data (DQL)
- 7 Logical Operators
- 8 Aggregate Functions
- 9 CASE Statement
- 10 SQL Joins
- 11 Subqueries
- 12 Union & Execution Order
Every query below runs against these tables. Refer back here anytime.
| id | name | last_name | country | city | age | |
|---|---|---|---|---|---|---|
| 1 | Arjun | Patel | India | Mumbai | 28 | arjun@mail.com |
| 2 | Sara | Doe | USA | New York | 32 | NULL |
| 3 | Kenji | Tanaka | Japan | Tokyo | 24 | kenji@mail.com |
| 4 | Anna | Klein | Germany | Berlin | 30 | anna@mail.com |
| 5 | James | Doe | USA | Chicago | 22 | james@mail.com |
| 6 | Priya | Sharma | India | Delhi | 27 | NULL |
| 7 | David | Brown | UK | London | 35 | david@mail.com |
| 8 | Sakura | Ito | Japan | Osaka | 19 | sakura@mail.com |
| id | name | price | category |
|---|---|---|---|
| 1 | Laptop | 1200 | Electronics |
| 2 | Headphones | 150 | Electronics |
| 3 | Notebook | 8 | Stationery |
| 4 | Desk Chair | 450 | Furniture |
| 5 | Monitor | 600 | Electronics |
| 6 | Pen Set | 25 | Stationery |
| id | customer_id | product_id | quantity |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
| 2 | 3 | 2 | 2 |
| 3 | 4 | 4 | 1 |
| 4 | 2 | 5 | 3 |
| 5 | 1 | 3 | 5 |
| id | name | score |
|---|---|---|
| 1 | Ravi | 92 |
| 2 | Mia | 85 |
| 3 | Chen | 73 |
| 4 | Emma | 61 |
| 5 | Kai | 45 |
| id | order_id | status |
|---|---|---|
| 1 | 1 | Delivered |
| 2 | 2 | In Transit |
| 3 | 4 | Delivered |
| 4 | 99 | Returned |
Part I — Database Foundations
What databases are, why we need them, and how keys keep data organized.
What is a Database
Definition
A database is an organized collection of data stored so it can be easily accessed, managed, and updated. Think of it as a digital filing cabinet — but one that can sort, search, and cross-reference instantly.
A DBMS (Database Management System) is the software that sits between users and the database. It enforces rules that protect the data:
| Concern | What it means |
|---|---|
| Data Integrity | Prevents duplicate, incorrect, or broken data from being stored |
| Security | Protects sensitive data from unauthorized access or misuse |
| Concurrency | Allows multiple users to read/write simultaneously without inconsistency |
Types of Databases
| Type | How data is organized | Best for |
|---|---|---|
| Relational | Tables with rows and columns, linked by keys | Structured data with clear relationships |
| NoSQL | Documents, key-value pairs, graphs, or wide columns | Flexible schemas, large-scale unstructured data |
RDBMS Properties
Every relational database table must follow four rules:
1. Every row must be unique
No two rows can have identical values across all columns.
| name | age | id_number |
|---|---|---|
| Himanshu | 80 | 801046122222 |
| Rahul | 92 | 801034569367 |
| Krish | 95 | 801025683456 |
| Rahul | 92 | 801034569367 |
2. Column values must share the same data type
If a column is defined as INT, every value in it must be a number. You can't mix types.
| name | age |
|---|---|
| Himanshu | 80 |
| Rahul | 92 |
| Rohit | Very Old |
3. Values must be atomic (single value per cell)
Each cell holds exactly one value. No lists, no comma-separated entries.
| name | phone |
|---|---|
| Himanshu | 8010461222 |
| Krish | 8010256834, 9427823456 |
4. Every column name must be unique
No two columns in the same table can share a name. This ensures every column can be referenced unambiguously.
Types of Keys
Keys are columns (or combinations of columns) that uniquely identify rows and create relationships between tables. Without keys, you can't reliably tell one "Rahul" from another.
Primary Key
A column (or group of columns) that uniquely identifies each row. Two rules: values must be UNIQUE and never NULL. Each table can have only one primary key.
| emp_id (PK) | name | licence | passport |
|---|---|---|---|
| 101 | Ravi | DL-4421 | P-9981 |
| 102 | Mia | DL-5532 | P-7762 |
| 103 | Chen | DL-6643 | P-5543 |
Every emp_id is unique and non-null — it can pinpoint exactly one employee.
Candidate Key
Any column (or set of columns) that could serve as the primary key — it's unique and non-null. The primary key is chosen from the candidates.
In the table above, emp_id, licence, and passport are all candidate keys because each uniquely identifies a row.
Super Key
Any set of columns that can identify a row — including extra columns beyond what's strictly needed. Every candidate key is a super key, but not every super key is minimal.
{emp_id} -- minimal (candidate key)
{emp_id, name} -- super key (emp_id alone is enough)
{licence, name} -- super key
{emp_id, passport, name} -- super key
Any set containing at least one candidate key is a super key.
Foreign Key
A column in one table that references the primary key of another table, creating a link between them. Unlike primary keys, foreign keys can have duplicates and NULLs.
| order_id (PK) | customer_id (FK) | product_id | quantity |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
| 2 | 3 | 2 | 2 |
| 3 | 4 | 4 | 1 |
| 4 | 2 | 5 | 3 |
| 5 | 1 | 3 | 5 |
Here, customer_id in orders points to id in the customers table. Customer 1 (Arjun) has two orders. The foreign key is what makes that relationship possible.
Quick Reference
| Key | Unique? | NULL? | Per table |
|---|---|---|---|
| Primary | Yes | No | Exactly one |
| Candidate | Yes | No | One or more |
| Super | Yes (may be non-minimal) | Depends | Many |
| Foreign | No (duplicates allowed) | Yes | Zero or more |
Part II — SQL Basics
Creating tables, inserting data, and writing your first queries.
DDL — Data Definition Language
DDL commands define the structure of your database — creating, modifying, and deleting databases and tables. They don't touch the data inside.
Database Commands
| Command | What it does |
|---|---|
| CREATE DATABASE db; | Creates a new database |
| SHOW DATABASES; | Lists all databases on the server |
| USE db; | Switches to a database (makes it active) |
| SELECT DATABASE(); | Shows which database is currently selected |
| SHOW TABLES; | Lists all tables in the active database |
| DROP DATABASE db; | Deletes an entire database (irreversible) |
CREATE TABLE
Defines a new table with columns and their data types.
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
last_name VARCHAR(50),
country VARCHAR(50),
city VARCHAR(50),
age INT,
email VARCHAR(100)
);
Each column needs a name and a data type. Common types:
| Type | Stores | Example |
|---|---|---|
| INT | Whole numbers | age, quantity, id |
| VARCHAR(n) | Variable-length text up to n chars | name, email |
| TEXT | Long text (no limit specified) | description, bio |
| DECIMAL(p,s) | Precise decimal numbers | price, salary |
| DATE | Date values | order_date |
SQL Constraints
Rules you attach to columns to control what data is allowed.
| Constraint | Rule |
|---|---|
| PRIMARY KEY | Unique + Not Null. Identifies each row. |
| NOT NULL | Column cannot be empty |
| UNIQUE | All values must be different |
| DEFAULT val | Uses a default value if none is provided |
| FOREIGN KEY | References a primary key in another table |
| CHECK | Validates a condition (e.g., age > 0) |
ALTER TABLE
Modifies an existing table — add, rename, change, or drop columns.
ALTER TABLE customers ADD phone VARCHAR(10);
ALTER TABLE customers RENAME COLUMN city TO location;
ALTER TABLE customers MODIFY COLUMN age VARCHAR(3);
ALTER TABLE customers DROP COLUMN email;
ALTER TABLE customers RENAME TO clients;
DROP TABLE
Permanently deletes a table and all its data.
DROP TABLE customers;DML — Data Manipulation Language
DML commands change the data inside tables — inserting new rows, updating existing ones, or deleting them.
INSERT INTO
Adds new rows to a table.
INSERT INTO customers (id, name, last_name, country, city, age, email)
VALUES (9, 'Liam', 'Park', 'Korea', 'Seoul', 26, 'liam@mail.com');
| id | name | last_name | country | city | age | |
|---|---|---|---|---|---|---|
| 1 | Arjun | Patel | India | Mumbai | 28 | arjun@mail.com |
| ... rows 2–8 ... | ||||||
| 9 | Liam | Park | Korea | Seoul | 26 | liam@mail.com |
INSERT INTO customers (id, name, last_name, country, city, age, email)
VALUES
(9, 'Liam', 'Park', 'Korea', 'Seoul', 26, 'liam@mail.com'),
(10, 'Zara', 'Ali', 'India', 'Pune', 23, 'zara@mail.com');
UPDATE
Modifies existing rows. Always use WHERE to target specific rows.
UPDATE customers
SET city = 'Manchester'
WHERE id = 7;
| id | name | city (before) | city (after) |
|---|---|---|---|
| 7 | David | London | Manchester |
DELETE
Removes rows from a table.
DELETE FROM customers
WHERE country = 'UK';
| id | name | country |
|---|---|---|
| 1 | Arjun | India |
| 2 | Sara | USA |
| 3 | Kenji | Japan |
| 4 | Anna | Germany |
| 5 | James | USA |
| 6 | Priya | India |
| 8 | Sakura | Japan |
DELETE vs TRUNCATE
| DELETE FROM table; | TRUNCATE TABLE table; |
|---|---|
| Removes all rows one by one | Drops and recreates the table (faster) |
| Can use WHERE to target specific rows | Always removes all rows |
| Can be rolled back (if inside a transaction) | Cannot be rolled back |
Querying Data — SELECT & Friends
DQL (Data Query Language) is really just one command — SELECT — with many clauses that shape what comes back.
SELECT & SELECT *
Retrieves data from a table. Use * for all columns or list specific ones.
SELECT name, country FROM customers;
| name | country |
|---|---|
| Arjun | India |
| Sara | USA |
| Kenji | Japan |
| Anna | Germany |
| James | USA |
| Priya | India |
| David | UK |
| Sakura | Japan |
WHERE Clause
Filters rows based on a condition. Only matching rows appear in the result.
SELECT name, age, country FROM customers
WHERE country = 'USA';
| name | age | country |
|---|---|---|
| Sara | 32 | USA |
| James | 22 | USA |
Comparison Operators
Used inside WHERE to compare values. They return 1 (true) or 0 (false).
| Operator | Meaning | Example |
|---|---|---|
| = | Equal to | WHERE age = 25 |
| != or <> | Not equal to | WHERE age != 25 |
| > | Greater than | WHERE age > 25 |
| < | Less than | WHERE age < 25 |
| >= | Greater or equal | WHERE age >= 25 |
| <= | Less or equal | WHERE age <= 25 |
AS — Column Aliases
Gives a column a temporary display name in the result set.
SELECT name AS customer_name, age AS years_old
FROM customers;
| customer_name | years_old |
|---|---|
| Arjun | 28 |
| Sara | 32 |
| Kenji | 24 |
The alias only exists in the query output — it doesn't rename the actual column.
ORDER BY
Sorts results. ASC (ascending, default) or DESC (descending).
SELECT name, age FROM customers
ORDER BY age ASC;
| name | age |
|---|---|
| Sakura | 19 |
| James | 22 |
| Kenji | 24 |
| Priya | 27 |
| Arjun | 28 |
| Anna | 30 |
| Sara | 32 |
| David | 35 |
SELECT name, country, age FROM customers
ORDER BY country ASC, age DESC;
| name | country | age |
|---|---|---|
| Anna | Germany | 30 |
| Arjun | India | 28 |
| Priya | India | 27 |
| Kenji | Japan | 24 |
| Sakura | Japan | 19 |
| David | UK | 35 |
| Sara | USA | 32 |
| James | USA | 22 |
LIMIT & OFFSET
LIMIT caps how many rows are returned. OFFSET skips rows before starting.
SELECT name, age FROM customers
ORDER BY age ASC
LIMIT 3;
| name | age |
|---|---|
| Sakura | 19 |
| James | 22 |
| Kenji | 24 |
SELECT name, age FROM customers
ORDER BY age ASC
LIMIT 3 OFFSET 2;
| name | age |
|---|---|
| Kenji | 24 |
| Priya | 27 |
| Arjun | 28 |
SELECT DISTINCT
Returns only unique values, removing duplicates.
SELECT DISTINCT country FROM customers;
| country |
|---|
| India |
| USA |
| Japan |
| Germany |
| UK |
SELECT DISTINCT country, city FROM customers;
| country | city |
|---|---|
| India | Mumbai |
| USA | New York |
| Japan | Tokyo |
| Germany | Berlin |
| USA | Chicago |
| India | Delhi |
| UK | London |
| Japan | Osaka |
Part III — Advanced Querying
Logical operators, aggregate functions, and conditional expressions.
Logical Operators
Logical operators combine or negate conditions in WHERE clauses. They return 1 (true) or 0 (false).
AND Operator
Selects data only if all conditions are true.
SELECT * FROM customers
WHERE country = 'India' AND age > 25;
| id | name | country | age |
|---|---|---|---|
| 1 | Arjun | India | 28 |
| 6 | Priya | India | 27 |
OR Operator
Selects data if any one condition is true.
SELECT * FROM customers
WHERE country = 'India' OR country = 'Japan';
| id | name | country |
|---|---|---|
| 1 | Arjun | India |
| 3 | Kenji | Japan |
| 6 | Priya | India |
| 8 | Sakura | Japan |
NOT Operator
Reverses a condition — selects rows where it's false.
SELECT * FROM customers WHERE NOT country = 'USA';
| id | name | country |
|---|---|---|
| 1 | Arjun | India |
| 3 | Kenji | Japan |
| 4 | Anna | Germany |
| 6 | Priya | India |
| 7 | David | UK |
| 8 | Sakura | Japan |
IN / NOT IN
IN matches against a list (shorthand for multiple ORs). NOT IN excludes the list.
SELECT * FROM customers
WHERE country IN ('India', 'Japan', 'Germany');
| id | name | country |
|---|---|---|
| 1 | Arjun | India |
| 3 | Kenji | Japan |
| 4 | Anna | Germany |
| 6 | Priya | India |
| 8 | Sakura | Japan |
BETWEEN / NOT BETWEEN
Selects values within a range (inclusive).
SELECT * FROM products WHERE price BETWEEN 100 AND 500;
| id | name | price |
|---|---|---|
| 2 | Headphones | 150 |
| 4 | Desk Chair | 450 |
LIKE & Wildcards
Pattern-matches strings.
| Wildcard | Meaning | Example |
|---|---|---|
| % | Zero or more characters | 'S%' — starts with S |
| _ | Exactly one character | '_r%' — r is 2nd letter
|
| [] | Any one char in brackets | '[UK]%' |
| ! | Exclude chars | '[!DR]%' |
SELECT * FROM customers WHERE name LIKE 'S%';
| id | name | country |
|---|---|---|
| 2 | Sara | USA |
| 8 | Sakura | Japan |
IS NULL / IS NOT NULL
Checks for missing values. You cannot use = or != for NULL.
SELECT * FROM customers WHERE email IS NULL;
| id | name | |
|---|---|---|
| 2 | Sara | NULL |
| 6 | Priya | NULL |
Aggregate Functions
Aggregate functions calculate a single value from a set of rows. Often paired with GROUP BY.
| Function | What it does |
|---|---|
| MIN() | Smallest value |
| MAX() | Largest value |
| COUNT() | Number of rows |
| SUM() | Total of a numeric column |
| AVG() | Average of a numeric column |
MAX() and MIN()
SELECT MAX(price) AS highest FROM products;
SELECT MIN(price) AS lowest FROM products;| highest | lowest |
|---|---|
| 1200 | 8 |
Nested SELECT to get the full row:
SELECT * FROM products
WHERE price = (SELECT MAX(price) FROM products);| id | name | price | category |
|---|---|---|---|
| 1 | Laptop | 1200 | Electronics |
COUNT()
SELECT COUNT(*) AS total FROM customers; -- 8
SELECT COUNT(email) AS has_email FROM customers; -- 6
SELECT COUNT(*) FROM customers WHERE country = 'India'; -- 2
SELECT COUNT(DISTINCT country) FROM customers; -- 5
| Counted | Skipped (NULL) |
|---|---|
| Arjun, Kenji, Anna, James, David, Sakura | Sara, Priya |
SUM() and AVG()
SELECT SUM(quantity) AS total_items FROM orders; -- 12
SELECT AVG(price) AS avg_price FROM products; -- 405.50| total_items | avg_price |
|---|---|
| 12 | 405.50 |
GROUP BY
Groups rows, then runs aggregates per group.
SELECT country, COUNT(*) AS total
FROM customers GROUP BY country;| country | total |
|---|---|
| India | 2 |
| USA | 2 |
| Japan | 2 |
| Germany | 1 |
| UK | 1 |
HAVING
Filters groups after aggregation (WHERE can't do this).
SELECT country, COUNT(*) AS total
FROM customers GROUP BY country
HAVING COUNT(*) > 1;| country | total |
|---|---|
| India | 2 |
| USA | 2 |
| Japan | 2 |
HAVING vs WHERE
| WHERE | HAVING |
|---|---|
| Filters rows before grouping | Filters groups after aggregation |
| Cannot use aggregate functions | Designed for aggregate functions |
| Comes before GROUP BY | Comes after GROUP BY |
CASE Statement
Works like if-else inside a query. Evaluates conditions and adds a new column based on which one matches first.
Basic CASE
SELECT name, price,
CASE
WHEN price > 1000 THEN 'Expensive'
WHEN price > 500 THEN 'Mid-range'
END AS price_category
FROM products;| name | price | price_category |
|---|---|---|
| Laptop | 1200 | Expensive |
| Headphones | 150 | NULL |
| Notebook | 8 | NULL |
| Desk Chair | 450 | NULL |
| Monitor | 600 | Mid-range |
| Pen Set | 25 | NULL |
CASE with ELSE
ELSE catches all unmatched rows so nothing becomes NULL.
SELECT name, score,
CASE
WHEN score >= 90 THEN 'A'
WHEN score >= 80 THEN 'B'
WHEN score >= 70 THEN 'C'
WHEN score >= 60 THEN 'D'
ELSE 'F'
END AS grade
FROM students;| name | score | grade |
|---|---|---|
| Ravi | 92 | A |
| Mia | 85 | B |
| Chen | 73 | C |
| Emma | 61 | D |
| Kai | 45 | F |
Part IV — Joins, Subqueries & Unions
Combining data from multiple tables and nesting queries inside queries.
SQL Joins
So far, every query has worked with a single table. JOIN combines rows from two or more tables using a common column, letting you pull related data together in one result.
| Join Type | What it returns |
|---|---|
| INNER JOIN | Only rows that match in both tables |
| LEFT JOIN | All rows from the left table + matches from the right (NULLs if no match) |
| RIGHT JOIN | All rows from the right table + matches from the left (NULLs if no match) |
| FULL JOIN | All rows from both tables (NULLs where no match on either side) |
INNER JOIN
Returns only rows with matching values in both tables. Rows that don't match are excluded entirely.
SELECT c.name, c.country, o.product_id, o.quantity
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id;
| name | country | product_id | quantity |
|---|---|---|---|
| Arjun | India | 1 | 1 |
| Arjun | India | 3 | 5 |
| Sara | USA | 5 | 3 |
| Kenji | Japan | 2 | 2 |
| Anna | Germany | 4 | 1 |
James, Priya, David, and Sakura have no orders — they don't appear at all.
LEFT JOIN
Returns all rows from the left table, plus matches from the right. Unmatched right-side columns become NULL.
SELECT c.name, c.country, o.product_id, o.quantity
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;
| name | country | product_id | quantity |
|---|---|---|---|
| Arjun | India | 1 | 1 |
| Arjun | India | 3 | 5 |
| Sara | USA | 5 | 3 |
| Kenji | Japan | 2 | 2 |
| Anna | Germany | 4 | 1 |
| James | USA | NULL | NULL |
| Priya | India | NULL | NULL |
| David | UK | NULL | NULL |
| Sakura | Japan | NULL | NULL |
Customers without orders still appear — their order columns are filled with NULL.
Handling NULLs with COALESCE()
COALESCE() replaces NULL with a default value — useful after LEFT/RIGHT joins.
SELECT c.name,
COALESCE(o.quantity, 0) AS quantity
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;Now instead of NULL, customers without orders show 0.
RIGHT JOIN
Opposite of LEFT — returns all rows from the right table, plus matches from the left.
SELECT o.id AS order_id, o.quantity, s.status
FROM orders o
RIGHT JOIN shipping s ON o.id = s.order_id;
| order_id | quantity | status |
|---|---|---|
| 1 | 1 | Delivered |
| 2 | 2 | In Transit |
| 4 | 3 | Delivered |
| NULL | NULL | Returned |
The "Returned" shipping record references order_id 99 which doesn't exist — LEFT columns become NULL.
FULL OUTER JOIN
Returns all rows from both tables. NULLs fill in wherever there's no match on either side.
SELECT o.id AS order_id, o.quantity, s.status
FROM orders o
FULL OUTER JOIN shipping s ON o.id = s.order_id;
| order_id | quantity | status |
|---|---|---|
| 1 | 1 | Delivered |
| 2 | 2 | In Transit |
| 3 | 1 | NULL |
| 4 | 3 | Delivered |
| 5 | 5 | NULL |
| NULL | NULL | Returned |
Orders 3 and 5 have no shipping. Shipping id=4 has no matching order. Both sides are kept.
Joining Multiple Tables
Chain multiple JOINs to connect 3+ tables.
SELECT c.name, p.name AS product, s.status
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products p ON o.product_id = p.id
LEFT JOIN shipping s ON o.id = s.order_id;
| name | product | status |
|---|---|---|
| Arjun | Laptop | Delivered |
| Kenji | Headphones | In Transit |
| Anna | Desk Chair | NULL |
| Sara | Monitor | Delivered |
| Arjun | Notebook | NULL |
SELF JOIN
Joins a table with itself. Requires aliases to distinguish the two copies.
SELECT c1.name AS customer_1,
c2.name AS customer_2,
c1.country
FROM customers c1
INNER JOIN customers c2
ON c1.country = c2.country
AND c1.id < c2.id;
| customer_1 | customer_2 | country |
|---|---|---|
| Arjun | Priya | India |
| Sara | James | USA |
| Kenji | Sakura | Japan |
c1.id < c2.id prevents duplicates (Arjun–Priya appears once, not Priya–Arjun too).
CTE — Common Table Expressions
A CTE creates a temporary named result set you can reference in the main query. Makes complex queries more readable.
WITH cte_name AS (
SELECT ...
)
SELECT * FROM cte_name;
WITH indian_customers AS (
SELECT id, name FROM customers
WHERE country = 'India'
)
SELECT ic.name, o.product_id, o.quantity
FROM indian_customers ic
INNER JOIN orders o ON ic.id = o.customer_id;
| name | product_id | quantity |
|---|---|---|
| Arjun | 1 | 1 |
| Arjun | 3 | 5 |
Priya has no orders, so she doesn't appear. The CTE acts like a temporary table that exists only for this query.
Subqueries
A subquery is a query nested inside another query. The inner query runs first, and its result is used by the outer query. Subqueries can return:
| Type | Returns | Use with |
|---|---|---|
| Single row | One value | = > < >= <=
|
| Multiple rows | A list of values | IN, ANY, ALL |
| Multiple columns | Row(s) with several columns | Multi-column comparison |
Single Row Subquery
Returns one value — used with comparison operators (=, >, <, etc.).
SELECT * FROM customers
WHERE age = (SELECT MIN(age) FROM customers);
| id | name | country | age |
|---|---|---|---|
| 8 | Sakura | Japan | 19 |
Subquery with IN
When the subquery returns multiple rows, use IN to match against the list.
SELECT name, country FROM customers
WHERE id IN (SELECT customer_id FROM orders);
| name | country |
|---|---|
| Arjun | India |
| Sara | USA |
| Kenji | Japan |
| Anna | Germany |
ANY Operator
ANY returns true if the condition matches at least one value from the subquery.
SELECT name, age FROM customers
WHERE age < ANY (
SELECT age FROM customers WHERE country = 'India'
);
| name | age |
|---|---|
| Kenji | 24 |
| Priya | 27 |
| James | 22 |
| Sakura | 19 |
< ANY effectively means "less than the largest value in the list."
ALL Operator
ALL returns true only if the condition matches every value from the subquery.
SELECT name, age FROM customers
WHERE age > ALL (
SELECT age FROM customers WHERE country = 'India'
);
| name | age |
|---|---|
| Sara | 32 |
| Anna | 30 |
| David | 35 |
> ALL effectively means "greater than the largest." < ALL means "less than the smallest."
EXISTS Operator
EXISTS checks whether a subquery returns any rows at all. It doesn't care about values — just "is there at least one row?"
SELECT name, country FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
);
| name | country |
|---|---|
| Arjun | India |
| Sara | USA |
| Kenji | Japan |
| Anna | Germany |
Subquery vs JOIN
Many subqueries can be rewritten as JOINs and vice versa. The result is the same — pick whichever is more readable.
SELECT name FROM customers
WHERE id IN (SELECT customer_id FROM orders);
SELECT DISTINCT c.name
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id;
The JOIN version needs DISTINCT because Arjun has two orders and would appear twice otherwise.
UNION & Execution Order
UNION
UNION combines the results of two SELECT statements into one result set, removing duplicates.
SELECT name FROM customers WHERE country = 'India'
UNION
SELECT name FROM products WHERE category = 'Electronics';
| name |
|---|
| Arjun |
| Priya |
| Laptop |
| Headphones |
| Monitor |
UNION ALL
Same as UNION but keeps duplicates. Faster because it skips the dedup step.
SELECT country FROM customers WHERE id <= 3
UNION ALL
SELECT country FROM customers WHERE id >= 3;
| country |
|---|
| India |
| USA |
| Japan |
| Japan |
| Germany |
| USA |
| India |
| UK |
| Japan |
UNION Rules
| Rule | Why |
|---|---|
| Same # of columns | Both SELECTs must return the same number of columns |
| Matching data types | Columns must be compatible types (both INT, both VARCHAR, etc.) |
| Same column order | Columns are matched by position, not by name |
UNION vs JOIN
| UNION | JOIN |
|---|---|
| Stacks rows vertically | Combines columns horizontally |
| Tables don't need a common column | Requires a common column to match on |
| Must have same number of columns | Can have different number of columns |
SQL Execution Order
SQL doesn't execute in the order you write it. Understanding the real execution order explains why certain things work (or don't).
-- YOU WRITE: -- ENGINE EXECUTES:
SELECT ... 5 FROM ... 1
FROM ... 1 WHERE ... 2
WHERE ... 2 GROUP BY ... 3
GROUP BY ... 3 HAVING ... 4
HAVING ... 4 SELECT ... 5
ORDER BY ... 6 ORDER BY ... 6
LIMIT ... 7 LIMIT ... 7
This is why you can't use a column alias from SELECT inside WHERE — WHERE runs before SELECT. But you can use it in ORDER BY, since that runs after.