-- Select all columns (avoid in production — be explicit)
SELECT*FROM customers;
-- Aliasing columns
SELECTnameAS customer_name, city ASlocation
FROM customers;
-- Computed columns
SELECTname, price, price *0.9AS discounted_price
FROM products;
name
price
discounted_price
Laptop
999.99
899.99
Wireless Mouse
29.99
26.99
Python Book
45.00
40.50
Standing Desk
349.99
314.99
USB-C Cable
12.99
11.69
WHERE Clause — Filtering Rows
-- Simple comparison
SELECTname, price FROM products
WHERE price >100;
name
price
Laptop
999.99
Standing Desk
349.99
-- Multiple conditions
SELECTname, price, category FROM products
WHERE category ='Electronics'AND price <50;
name
price
category
Wireless Mouse
29.99
Electronics
USB-C Cable
12.99
Electronics
-- IN operator
SELECTname, city FROM customers
WHERE city IN ('New York', 'Chicago');
-- BETWEEN operator
SELECTname, price FROM products
WHERE price BETWEEN20AND100;
-- LIKE for pattern matching
SELECTname, email FROM customers
WHERE email LIKE'%@example.com';
-- NULL checks
SELECTname, city FROM customers
WHERE city IS NOT NULL;
ORDER BY and LIMIT
-- Sort by price descending, take top 3
SELECTname, price FROM products
ORDER BY price DESC
LIMIT3;
name
price
Laptop
999.99
Standing Desk
349.99
Python Book
45.00
-- Pagination: page 2, 2 items per page
SELECTname, price FROM products
ORDER BY price DESC
LIMIT2 OFFSET 2;
name
price
Python Book
45.00
Wireless Mouse
29.99
DISTINCT
SELECT DISTINCT city FROM customers;
city
New York
Chicago
San Francisco
Aggregate Functions
Aggregate functions compute a single result from a set of rows.
Function
Description
Example
COUNT()
Number of rows
COUNT(*), COUNT(column)
SUM()
Sum of values
SUM(price)
AVG()
Average of values
AVG(price)
MIN()
Minimum value
MIN(price)
MAX()
Maximum value
MAX(price)
SELECT
COUNT(*) AS total_products,
SUM(price) AS total_value,
AVG(price) AS avg_price,
MIN(price) AS cheapest,
MAX(price) AS most_expensive
FROM products;
total_products
total_value
avg_price
cheapest
most_expensive
5
1437.96
287.59
12.99
999.99
GROUP BY and HAVING
GROUP BY partitions rows into groups and applies aggregate functions to each group. HAVING filters groups after aggregation (like WHERE but for groups).
SELECT
category,
COUNT(*) AS num_products,
AVG(price) AS avg_price
FROM products
GROUP BY category;
category
num_products
avg_price
Electronics
3
347.66
Books
1
45.00
Furniture
1
349.99
-- Only categories with average price above 100
SELECT
category,
COUNT(*) AS num_products,
AVG(price) AS avg_price
FROM products
GROUP BY category
HAVINGAVG(price) >100;
category
num_products
avg_price
Electronics
3
347.66
Furniture
1
349.99
JOINs
JOINs combine rows from two or more tables based on a related column. They are fundamental to working with relational databases.
Visual Overview
Table A Table B JOIN Types
┌─────┐ ┌─────┐
│ 1 │ │ 1 │ INNER JOIN: Only matching rows
│ 2 │ │ 2 │ LEFT JOIN: All from A + matches from B
│ 3 │ │ 3 │ RIGHT JOIN: All from B + matches from A
│ 4 │ │ 5 │ FULL OUTER JOIN: All from both
└─────┘ └─────┘ CROSS JOIN: Every combination (A x B)
INNER JOIN (A ∩ B) LEFT JOIN RIGHT JOIN FULL OUTER JOIN
Returns only rows that have matching values in both tables.
SELECT
c.nameAS customer,
o.order_id,
o.order_date,
o.status
FROM customers c
INNER JOIN orders o ONc.customer_id=o.customer_id;
customer
order_id
order_date
status
Alice Johnson
1
2024-01-15
completed
Alice Johnson
2
2024-02-20
completed
Bob Smith
3
2024-03-10
pending
LEFT JOIN (LEFT OUTER JOIN)
Returns all rows from the left table, and matching rows from the right table. Non-matching rows get NULL for the right table columns.
SELECT
c.nameAS customer,
o.order_id,
o.status
FROM customers c
LEFT JOIN orders o ONc.customer_id=o.customer_id;
customer
order_id
status
Alice Johnson
1
completed
Alice Johnson
2
completed
Bob Smith
3
pending
Carol Lee
NULL
NULL
David Kim
NULL
NULL
-- Find customers who have never placed an order
SELECTc.name
FROM customers c
LEFT JOIN orders o ONc.customer_id=o.customer_id
WHEREo.order_idISNULL;
name
Carol Lee
David Kim
RIGHT JOIN (RIGHT OUTER JOIN)
Returns all rows from the right table, and matching rows from the left table. This is the mirror of LEFT JOIN.
SELECT
c.nameAS customer,
o.order_id,
o.status
FROM customers c
RIGHT JOIN orders o ONc.customer_id=o.customer_id;
In practice, most developers prefer LEFT JOIN and rearrange the table order rather than using RIGHT JOIN.
FULL OUTER JOIN
Returns all rows from both tables. Non-matching rows on either side get NULL values.
SELECT
c.nameAS customer,
o.order_id
FROM customers c
FULL OUTER JOIN orders o ONc.customer_id=o.customer_id;
CROSS JOIN
Produces the Cartesian product — every row from the first table paired with every row from the second table. Use cautiously as output size = rows_A x rows_B.
-- Generate a report template: every customer x every product
SELECTc.name, p.nameAS product
FROM customers c
CROSS JOIN products p;
-- Result: 4 customers x 5 products = 20 rows
SELF JOIN
A table joined with itself. Useful for hierarchical data or comparing rows within the same table.
-- Find customers in the same city
SELECT
a.nameAS customer_1,
b.nameAS customer_2,
a.city
FROM customers a
INNER JOIN customers b
ONa.city=b.city
ANDa.customer_id<b.customer_id;
customer_1
customer_2
city
Alice Johnson
Carol Lee
New York
Multi-Table JOINs
Real queries often join three or more tables:
-- Full order details: customer, order, items, products
SELECT
c.nameAS customer,
o.order_id,
o.order_date,
p.nameAS product,
oi.quantity,
oi.unit_price,
(oi.quantity*oi.unit_price) AS line_total
FROM customers c
INNER JOIN orders o ONc.customer_id=o.customer_id
INNER JOIN order_items oi ONo.order_id=oi.order_id
INNER JOIN products p ONoi.product_id=p.product_id
ORDER BYo.order_id, p.name;
customer
order_id
order_date
product
quantity
unit_price
line_total
Alice Johnson
1
2024-01-15
Laptop
1
999.99
999.99
Alice Johnson
1
2024-01-15
Wireless Mouse
1
29.99
29.99
Alice Johnson
2
2024-02-20
Python Book
2
45.00
90.00
Bob Smith
3
2024-03-10
Standing Desk
1
349.99
349.99
Bob Smith
3
2024-03-10
USB-C Cable
3
12.99
38.97
ORM Equivalents
Here is how a common multi-table query looks in SQL versus popular ORMs:
Window functions perform calculations across a set of rows that are related to the current row, without collapsing the result into a single row (unlike GROUP BY).
Syntax
function_name(args) OVER (
[PARTITION BY column1, column2, ...]
[ORDER BY column3, column4, ...]
[frame_clause]
)
ROW_NUMBER, RANK, and DENSE_RANK
SELECT
name,
category,
price,
ROW_NUMBER() OVER (ORDER BY price DESC) AS row_num,
RANK() OVER (ORDER BY price DESC) AS rank,
DENSE_RANK() OVER (ORDER BY price DESC) AS dense_rank
FROM products;
name
category
price
row_num
rank
dense_rank
Laptop
Electronics
999.99
1
1
1
Standing Desk
Furniture
349.99
2
2
2
Python Book
Books
45.00
3
3
3
Wireless Mouse
Electronics
29.99
4
4
4
USB-C Cable
Electronics
12.99
5
5
5
PARTITION BY
PARTITION BY creates separate windows for each group:
-- Rank products within each category by price
SELECT
name,
category,
price,
ROW_NUMBER() OVER (
PARTITIONBY category
ORDER BY price DESC
) AS rank_in_category
FROM products;
name
category
price
rank_in_category
Python Book
Books
45.00
1
Laptop
Electronics
999.99
1
Wireless Mouse
Electronics
29.99
2
USB-C Cable
Electronics
12.99
3
Standing Desk
Furniture
349.99
1
LAG and LEAD
Access values from previous or subsequent rows:
-- Compare each product's price to the next cheaper product
SELECT
name,
price,
LAG(price, 1) OVER (ORDER BY price DESC) AS prev_higher_price,
LEAD(price, 1) OVER (ORDER BY price DESC) AS next_lower_price,
price -LEAD(price, 1) OVER (ORDER BY price DESC) AS price_gap
FROM products;
name
price
prev_higher_price
next_lower_price
price_gap
Laptop
999.99
NULL
349.99
650.00
Standing Desk
349.99
999.99
45.00
304.99
Python Book
45.00
349.99
29.99
15.01
Wireless Mouse
29.99
45.00
12.99
17.00
USB-C Cable
12.99
29.99
NULL
NULL
Running Totals with SUM OVER
-- Running total of order item revenue, ordered by date
SELECT
o.order_date,
p.nameAS product,
oi.quantity*oi.unit_priceAS line_total,
SUM(oi.quantity*oi.unit_price) OVER (
ORDER BYo.order_date
ROWSBETWEENUNBOUNDEDPRECEDINGAND CURRENT ROW
) AS running_total
FROM order_items oi
INNER JOIN orders o ONoi.order_id=o.order_id
INNER JOIN products p ONoi.product_id=p.product_id