Source: https://sqlbolt.com
SELECT column1, column2, ...
FROM table_name;SELECT *
FROM table_name;- SELECT retrieves data
- FROM specifies table
*means all columns
SELECT column1, ...
FROM table_name
WHERE condition
AND/OR another_condition
AND/OR ...;| Operator | Condition | Example |
|---|---|---|
= |
Equal to | age = 18 |
!= |
Not equal to | age != 18 |
< |
Less than | age < 18 |
<= |
Less than or equal | age <= 18 |
> |
Greater than | age > 18 |
>= |
Greater than or equal | age >= 18 |
BETWEEN ... AND ... |
Within range (inclusive) | age BETWEEN 18 AND 25 |
NOT BETWEEN ... AND ... |
Outside range | age NOT BETWEEN 18 AND 25 |
IN (...) |
Matches any value in list | age IN (18, 21, 25) |
NOT IN (...) |
Does not match any value in list | age NOT IN (18, 21, 25) |
- WHERE filters rows
SELECT column1, ...
FROM table_name
WHERE condition;| Operator | Meaning | Example |
|---|---|---|
= |
Exact match | title = "Toy Story" |
!= or <> |
Not equal | title != "Cars" |
LIKE |
Pattern match | title LIKE "Toy%" |
NOT LIKE |
Does not match pattern | title NOT LIKE "Toy%" |
IN (...) |
Matches any value in list | director IN ("John Lasseter", "Brad Bird") |
NOT IN (...) |
Does not match any value in list | director NOT IN ("John Lasseter") |
title LIKE "%Toy%"Matches:
- Toy Story
- Toy Story 2
title LIKE "WALL-_"Matches:
- WALL-E
- WALL-G
Does NOT match:
- WALL-EE
- Use
LIKEfor pattern matching %matches multiple characters_matches one character- Use
INfor matching multiple values - Always use quotes for text values
Returns only unique values from a column.
SELECT DISTINCT column1, column2, ...
FROM table_name
WHERE condition(s);Sorts query results in ascending or descending order.
SELECT column1, column2, ...
FROM table_name
ORDER BY column_name ASC;SELECT column_name
FROM table_name
ORDER BY column_name DESC;ASC→ ascending (default)DESC→ descending
Returns only a specified number of rows.
SELECT column1, column2, ...
FROM table_name
LIMIT num;Skips a specified number of rows before returning results.
SELECT column1, column2, ...
FROM table_name
LIMIT num_limit OFFSET num_offset;SELECT title, year
FROM movies
ORDER BY year DESC
LIMIT 5 OFFSET 5;Skips first 5 newest movies and returns the next 5 newest movies.
DISTINCTremoves duplicate valuesORDER BYsorts resultsASC= ascending,DESC= descendingLIMITrestricts number of rows returnedOFFSETskips rows before returning results
SELECT column1, column2, ...
FROM table_name
WHERE condition(s)
ORDER BY column ASC/DESC
LIMIT number OFFSET number;- Combine multiple clauses in one query
WHEREfilters rowsORDER BYsorts rowsLIMITrestricts resultsOFFSETskips rows- All clauses work together to answer specific questions
In normalized databases, related data is stored in separate tables.
Example:
movies→ movie infoboxoffice→ ratings and sales
JOIN combines related data using a common key (movie_id).
SELECT table1.column, table2.column
FROM table1
INNER JOIN table2
ON table1.common_column = table2.common_column
WHERE condition(s)
ORDER BY COLUMN, ... ASC/DESC
LIMIT num OFFSET num;- Matches rows from both tables
- Uses a shared key (
id,movie_id, etc.) - Returns only matching rows
- Combines columns from both tables
movies:
- id
- title
- director
- year
boxoffice:
- movie_id
- rating
- domestic_sales
- international_sales
Common key:
movies.id = boxoffice.movie_idUse this format when working with multiple tables:
table_name.column_nameExample:
movies.title
boxoffice.ratingPrevents ambiguity.
INNER JOINcombines rows from two tables- Uses common key with
ON - Returns only matching rows
- Use
table.columnformat - Can combine with
WHERE,ORDER BY,LIMIT
INNER JOIN returns only matching rows in both tables.
Unmatched rows are excluded.
| JOIN Type | Returns |
|---|---|
LEFT JOIN |
All rows from left table + matching rows from right table |
RIGHT JOIN |
All rows from right table + matching rows from left table |
FULL JOIN |
All rows from both tables |
Unmatched values appear as NULL(means no matching data exists).
SELECT table1.column, table2.column
FROM table1
LEFT/RIGHT/FULL JOIN table2
ON table1.common_column = table2.common_column
WHERE condition(s)
ORDER BY column, … ASC/DESC
LIMIT num_limit OFFSET num_offset;LEFT JOINkeeps all rows from left tableRIGHT JOINkeeps all rows from right tableFULL JOINkeeps all rows from both tables- Unmatched rows show
NULL - Use LEFT JOIN when you want all records from primary table
- INNER JOIN excludes unmatched rows
NULL means missing, unknown, or no value.
It is NOT:
- 0
- empty string ""
- false
It represents absence of data.
Occurs when:
- Data is missing
- Data is not assigned yet
- Using OUTER JOIN
- Incomplete records
Example:
name building
--------- --------
Oliver P. NULL
Use IS NULL or IS NOT NULL
building IS NULL
building IS NOT NULLSELECT column_name
FROM table_name
WHERE column_name IS NULL;SELECT column_name
FROM table_name
WHERE column_name IS NOT NULL;NULL is not equal to anything, including itself.
This fails:
building = NULLBecause NULL means unknown value.
NULLmeans missing or unknown data- Use
IS NULLto find missing values - Use
IS NOT NULLto find existing values - Cannot use
=or!=with NULL - Common when using OUTER JOIN
An expression performs calculations or transformations on column values.
Examples:
- Arithmetic operations
- Mathematical functions
- String functions
SELECT column1 + column2
FROM table_name;Aliases rename columns in the output for readability.
SELECT expression AS alias_name
FROM table_name;SELECT domestic_sales + international_sales AS total_sales
FROM boxoffice;Output column will be named total_sales.
SELECT (domestic_sales + international_sales) / 1000000 AS total_sales_millions
FROM boxoffice;SELECT rating * 10 AS rating_percent
FROM boxoffice;SELECT title, year
FROM movies
WHERE year % 2 = 0;% is modulo operator (returns remainder).
Used to shorten table names.
SELECT t.column_name
FROM table_name AS t;SELECT m.title,
(b.domestic_sales + b.international_sales) AS total_sales
FROM movies AS m
INNER JOIN boxoffice AS b
ON m.id = b.movie_id;- Expressions perform calculations in queries
- Use
ASto rename columns - Improves readability
- Can combine multiple columns
- Can use arithmetic operators
- Table aliases simplify complex queries
Aggregate functions perform calculations on multiple rows and return a single value.
Used for:
- Counting rows
- Finding averages
- Finding minimum/maximum values
- Finding totals
SELECT AGG_FUNCTION(column_name) AS alias_name
FROM table_name;| Function | Description |
|---|---|
COUNT(*) |
Counts total number of rows |
COUNT(column) |
Counts non-NULL values |
MIN(column) |
Finds smallest value |
MAX(column) |
Finds largest value |
AVG(column) |
Finds average value |
SUM(column) |
Finds total sum |
Groups rows with same value and applies aggregate function to each group.
SELECT column, AGG_FUNCTION(column_or_expression)
FROM table_name
GROUP BY column;Without GROUP BY:
- Aggregate runs on entire table
- Returns one row
With GROUP BY:
- Aggregate runs per group
- Returns multiple rows
- Aggregate functions summarize data
GROUP BYgroups rows for aggregation- Use
ASto name results clearly
WHERE filters rows before grouping.
But how do you filter after GROUP BY?
Solution: Use HAVING.
SELECT group_by_column, AGG_FUNC(column) AS agg_alias, ...
FROM table_name
WHERE condition
GROUP BY column
HAVING group_condition;| Clause | Filters |
|---|---|
| WHERE | Individual rows |
| HAVING | Groups |
HAVINGfilters grouped data- Used with
GROUP BY WHEREfilters rows before groupingHAVINGfilters after grouping- Can use aggregate functions in HAVING
SELECT DISTINCT column, AGG_FUNCTION(column_or_expression), ...
FROM table AS t
JOIN another_table as a
ON t.column = a.column
WHERE condition
GROUP BY column
HAVING constraint_expression
ORDER BY column ASC/DESC
LIMIT num OFFSET num;SQL executes queries in this order:
1. FROM / JOIN
2. WHERE
3. GROUP BY
4. HAVING
5. SELECT
6. DISTINCT
7. ORDER BY
8. LIMIT / OFFSET
- Selects tables
- Combines tables using JOIN
- Filters rows
- Removes unwanted rows
- Groups rows with same values
- Used with aggregate functions
- Filters grouped data
- Chooses columns
- Computes expressions
- Removes duplicate rows
- Sorts results
- Limits number of rows
- Skips rows
WHEREfilters before groupingHAVINGfilters after grouping- Cannot use SELECT alias in WHERE
- Can use SELECT alias in ORDER BY
- LIMIT runs last
FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT
- SQL does NOT execute in written order
- Execution order is fixed internally
- Understanding order prevents logical errors
- Essential for GROUP BY and HAVING queries
A schema defines:
- Table structure
- Column names
- Data types
Example table:
movies
------
id (INTEGER)
title (TEXT)
director (TEXT)
year (INTEGER)
length_minutes (INTEGER)
Insert values for all columns:
INSERT INTO table_name
VALUES (value1, value2, value3, ...);Example:
INSERT INTO movies
VALUES (15, "Toy Story 4", "Josh Cooley", 2019, 100);Insert values into selected columns:
INSERT INTO table_name (column1, column2, column3)
VALUES (value1, value2, value3);Example:
INSERT INTO movies (title, director, year, length_minutes)
VALUES ("Toy Story 4", "Josh Cooley", 2019, 100);Useful when:
- ID is auto-generated
- Some columns have default values
INSERT INTO table_name (column1, column2)
VALUES
(value1, value2),
(value3, value4);Example:
INSERT INTO movies (title, director, year, length_minutes)
VALUES
("Movie A", "Director A", 2020, 120),
("Movie B", "Director B", 2021, 110);Expressions can be used while inserting:
INSERT INTO boxoffice (movie_id, rating, domestic_sales)
VALUES (15, 8.7, 340000000 / 1000000);Insert into movies table:
INSERT INTO movies (title, director, year, length_minutes)
VALUES ("Toy Story 4", "Josh Cooley", 2019, 100);Insert into boxoffice table:
INSERT INTO boxoffice (movie_id, rating, domestic_sales, international_sales)
VALUES (15, 8.7, 340000000, 270000000);INSERT INTOadds new rows- Must match column order and values
- Can insert into specific columns
- Can insert multiple rows
- Can use expressions
- Useful for adding new data to database
UPDATE is used to modify existing data in a table.
UPDATE table
SET column = value_or_expression
WHERE condition;SETspecifies column and new valueWHEREspecifies which rows to update
UPDATE table_name
SET column1 = value1,
column2 = value2
WHERE condition;UPDATE movies
SET title = "Toy Story 3",
director = "Lee Unkrich"
WHERE id = 11;If you omit WHERE, ALL rows will be updated:
UPDATE movies
SET director = "Unknown"; -- affects entire tableAlways use WHERE carefully.
Test condition first with SELECT:
SELECT *
FROM movies
WHERE id = 11;Then update:
UPDATE movies
SET title = "Toy Story 3"
WHERE id = 11;UPDATE movies
SET year = year + 1
WHERE id = 3;UPDATEmodifies existing rows- Use
SETto assign new values - Use
WHEREto select specific rows - Without WHERE, all rows are updated
- Can update multiple columns
- Can use expressions
DELETE removes rows from a table.
DELETE FROM table_name
WHERE condition;WHEREspecifies which rows to delete- Only matching rows are removed
DELETE FROM table_name;Removes every row in the table.
DELETE FROM movies
WHERE year < 2005 AND director = "John Lasseter";DELETEremoves rows from a table- Use
WHEREto select specific rows - Without WHERE, all rows are deleted
- Always verify with SELECT first
- Deleted data cannot be recovered easily
CREATE TABLE is used to create a new table in a database.
Defines:
- Column names
- Data types
- Constraints
- Default values
CREATE TABLE table_name (
column_name DATA_TYPE,
column_name DATA_TYPE
);Prevents error if table already exists:
CREATE TABLE IF NOT EXISTS table_name (
column_name DATA_TYPE
);CREATE TABLE Database (
Name TEXT,
Version FLOAT,
Download_count INTEGER
);| Data Type | Description |
|---|---|
INTEGER |
Whole numbers |
FLOAT, REAL, DOUBLE |
Decimal numbers |
TEXT |
String or text |
CHAR(n) |
Fixed-length string |
VARCHAR(n) |
Variable-length string |
BOOLEAN |
True or false |
DATE |
Date |
DATETIME |
Date and time |
BLOB |
Binary data |
| Constraint | Description |
|---|---|
PRIMARY KEY |
Unique identifier for each row |
AUTOINCREMENT |
Automatically increments value |
UNIQUE |
No duplicate values |
NOT NULL |
Cannot store NULL |
CHECK |
Ensures condition is true |
FOREIGN KEY |
Links to another table |
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
age INTEGER CHECK(age >= 0)
);CREATE TABLEcreates new tables- Defines structure and schema
- Must specify column names and data types
- Constraints control allowed values
IF NOT EXISTSprevents errors- Required before inserting data
ALTER TABLE is used to modify an existing table.
Used to:
- Add columns
- Remove columns
- Rename table
- Modify schema
ALTER TABLE table_name
ADD column_name DATA_TYPE;ALTER TABLE table_name
ADD column_name DATA_TYPE OptionalTableConstraint
DEFAULT default_value;ALTER TABLE table_name
DROP column_name;ALTER TABLE table_name
RENAME TO new_table_name;ALTER TABLE movies
ADD Aspect_ratio FLOAT;
ALTER TABLE movies
ADD Language TEXT DEFAULT "English";- New column added to table
- Existing rows get default value or NULL
- New rows use default value if not specified
ALTER TABLEmodifies existing tablesADDadds new column- Can specify default value
RENAME TOrenames table- Some databases (SQLite) don't support DROP COLUMN
- Used to update schema without deleting table
DROP TABLE permanently removes a table and its data.
Removes:
- All rows
- All columns
- Table structure (schema)
DROP TABLE table_name;Avoids error if table does not exist:
DROP TABLE IF EXISTS table_name;DROP TABLE IF EXISTS movies;
DROP TABLE IF EXISTS boxoffice;| Command | Removes Data | Removes Table Structure |
|---|---|---|
| DELETE | Yes | No |
| DROP TABLE | Yes | Yes |
- Cannot recover dropped table easily
- All data is permanently lost
- Use carefully
If table is referenced by another table:
- Must remove dependency first
- Or drop dependent tables
DROP TABLEdeletes entire table- Removes both data and schema
- Use
IF EXISTSto prevent errors - Cannot be undone easily
- Different from DELETE
A subquery is a query inside another query.
Used to:
- Filter data
- Compare values
- Generate dynamic values
Subquery runs first, outer query runs second.
SELECT column_name
FROM table_name
WHERE column_name OPERATOR (
SELECT column_name
FROM table_name
);Used to match values from another table.
SELECT *
FROM employees
WHERE department IN (
SELECT department
FROM departments
);SELECT *
FROM (
SELECT title, year
FROM movies
) AS movie_list;Subquery acts like a temporary table.
Inner query depends on outer query.
Runs once per outer row.
SELECT *
FROM employees
WHERE salary > (
SELECT AVG(revenue_generated)
FROM employees AS dept
WHERE dept.department = employees.department
);Calculates average per department.
SELECT *
FROM movies
WHERE id IN (
SELECT movie_id
FROM boxoffice
);Returns movies that exist in boxoffice.
- Subquery = query inside query
- Written inside parentheses
() - Executes before outer query
- Can be used in SELECT, FROM, WHERE
- Correlated subquery runs per row
- Useful for dynamic filtering
Set operators combine results of multiple SELECT queries.
Requirements:
- Same number of columns
- Same column order
- Same data types
Combines results and removes duplicates.
SELECT column_name
FROM table1
UNION
SELECT column_name
FROM table2;Combines results and keeps duplicates.
SELECT column_name
FROM table1
UNION ALL
SELECT column_name
FROM table2;Returns only common rows in both queries.
SELECT column_name
FROM table1
INTERSECT
SELECT column_name
FROM table2;Returns rows from first query NOT in second query.
SELECT column_name
FROM table1
EXCEPT
SELECT column_name
FROM table2;Order matters.
SELECT → UNION / INTERSECT / EXCEPT → ORDER BY → LIMIT
| Operator | Result |
|---|---|
| UNION | Combines, removes duplicates |
| UNION ALL | Combines, keeps duplicates |
| INTERSECT | Common rows only |
| EXCEPT | Rows only in first query |
- Combines results from multiple queries
- Queries must have same structure
- UNION removes duplicates
- UNION ALL keeps duplicates
- INTERSECT finds common rows
- EXCEPT finds unique rows in first query
IN subquery works in 2 steps:
Step 1: Inner query creates a list
Step 2: Outer query checks each row against that list
customers:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Charlie |
| 4 | David |
orders:
| order_id | customer_id |
|---|---|
| 101 | 1 |
| 102 | 3 |
SELECT *
FROM customers
WHERE id IN (
SELECT customer_id
FROM orders
);SELECT customer_id FROM orders;Result:
[1, 3]
Database creates list:
LIST = [1, 3]
Alice id=1 → in list → YES
Bob id=2 → in list → NO
Charlie id=3 → in list → YES
David id=4 → in list → NO
Alice
Charlie
Build list → Check each row
IN subquery runs inner query ONCE.
Correlated subquery runs inner query for EACH row.
FOR EACH ROW → run inner query using that row
numbers:
| num |
|---|
| 1 |
| 2 |
| 3 |
| 4 |
SELECT num
FROM numbers n1
WHERE num > (
SELECT AVG(num)
FROM numbers n2
WHERE n2.num <= n1.num
);num = 1
inner query → avg(1) = 1
check → 1 > 1 → NO
num = 2
inner query → avg(1,2) = 1.5
check → 2 > 1.5 → YES
num = 3
inner query → avg(1,2,3) = 2
check → YES
num = 4
inner query → avg(1,2,3,4) = 2.5
check → YES
2
3
4
Inner query changes per row:
num=1 → run query using 1
num=2 → run query using 2
num=3 → run query using 3
num=4 → run query using 4
Correlated subquery runs MANY times (once per row).
Existence(IN) subquery:
Make list → check list
Correlated subquery:
For each row → run query again
JOIN:
Combine tables directly
Date completed: 21-02-2026
This document serves as:
- Personal SQL reference
- Revision guide
- Long-term knowledge base
End of SQLBolt Notes 💖