0% found this document useful (0 votes)
2 views25 pages

SQL Guide

Mastering SQL is a comprehensive guide designed for beginners, analysts, and developers, covering essential SQL concepts such as fundamentals, commands, joins, subqueries, window functions, and performance optimization. The document includes practical examples, interview questions, and practice problems to enhance understanding and proficiency in SQL. It serves as a valuable resource for anyone looking to master SQL for database management and data analysis.

Uploaded by

doom8356
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views25 pages

SQL Guide

Mastering SQL is a comprehensive guide designed for beginners, analysts, and developers, covering essential SQL concepts such as fundamentals, commands, joins, subqueries, window functions, and performance optimization. The document includes practical examples, interview questions, and practice problems to enhance understanding and proficiency in SQL. It serves as a valuable resource for anyone looking to master SQL for database management and data analysis.

Uploaded by

doom8356
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

MASTERING SQL

A Complete Guide for Beginners, Analysts & Developers

Covers SQL Fundamentals • Joins & Subqueries • Window Functions


Indexes & Performance • Stored Procedures • Interview Q&A; • Practice Problems

Mastering SQL — A Complete Guide | Page 1


CHAPTER

Table of Contents

Chapter 1 — SQL Fundamentals — Databases, Tables & Data Types


Chapter 2 — Core SQL Commands — DDL, DML, DQL & DCL
Chapter 3 — Filtering, Sorting & Aggregation
Chapter 4 — Joins — Combining Tables
Chapter 5 — Subqueries & Common Table Expressions (CTEs)
Chapter 6 — Window Functions
Chapter 7 — Indexes & Query Performance
Chapter 8 — Stored Procedures, Functions & Triggers
Chapter 9 — Transactions & Concurrency
Chapter 10 — Interview Questions & Practice Problems

Mastering SQL — A Complete Guide | Page 2


CHAPTER 1

SQL Fundamentals
Databases, Tables, Data Types & the Relational Model

What is SQL?
SQL (Structured Query Language) is the standard language for managing and manipulating relational
databases. Originally developed at IBM in the 1970s, SQL is now supported by virtually every major
database system: MySQL, PostgreSQL, Microsoft SQL Server, Oracle, SQLite, and more. Despite minor
dialect differences between vendors, roughly 90% of SQL you write is portable across all of them.

A relational database stores data in structured tables (also called relations), where each table has named
columns (attributes) and rows (records/tuples). Tables can be linked to each other through foreign keys,
eliminating redundancy and enabling powerful cross-table queries.

Data Types — Choosing the Right Type


Choosing the correct data type is one of the most impactful decisions you make when designing a database.
It affects storage size, query speed, and data integrity.

Category Data Type Description Example

Numeric INT / INTEGER Whole numbers, 4 bytes age INT

Numeric BIGINT Large whole numbers, 8 bytes user_id BIGINT

Numeric DECIMAL(p,s) Exact precision for money price DECIMAL(10,2)

Numeric FLOAT / DOUBLE Approximate decimal (scientific use) ratio FLOAT

Text VARCHAR(n) Variable-length string up to n chars name VARCHAR(100)

Text CHAR(n) Fixed-length string, padded with spaces code CHAR(3)

Text TEXT Unlimited text (not indexable in full) description TEXT

Date/Time DATE Calendar date only (YYYY-MM-DD) birth_date DATE

created_at
Date/Time TIMESTAMP Date + time, often UTC-aware
TIMESTAMP

Boolean BOOLEAN TRUE / FALSE (stored as 1/0 in many DBs) is_active BOOLEAN

Binary BLOB Binary large object (images, files) photo BLOB

Primary Keys, Foreign Keys & Constraints

Mastering SQL — A Complete Guide | Page 3


Constraints enforce rules at the database level, preventing bad data from ever being stored. They are your
first line of defence for data quality.

• PRIMARY KEY — Uniquely identifies each row. Cannot be NULL. Every table should have one.
• FOREIGN KEY — Links a column in one table to the PRIMARY KEY of another, enforcing referential
integrity.
• UNIQUE — Ensures all values in a column (or group of columns) are distinct.
• NOT NULL — Prevents NULL values from being stored in a column.
• CHECK — Validates that a value meets a condition (e.g. age > 0).
• DEFAULT — Automatically assigns a value when one is not provided on INSERT.

CREATE TABLE employees (

employee_id INT PRIMARY KEY AUTO_INCREMENT,

full_name VARCHAR(150) NOT NULL,

email VARCHAR(200) UNIQUE NOT NULL,

department_id INT NOT NULL,

salary DECIMAL(12,2) CHECK (salary >= 0),

hired_on DATE DEFAULT (CURRENT_DATE),

FOREIGN KEY (department_id) REFERENCES departments(department_id)

);

PRO TIP: Always define a PRIMARY KEY on every table. Use BIGINT AUTO_INCREMENT (MySQL) or
SERIAL (PostgreSQL) for surrogate keys in large systems.

Mastering SQL — A Complete Guide | Page 4


CHAPTER 2

Core SQL Commands


DDL, DML, DQL & DCL — The Four Languages Within SQL

SQL is divided into four sub-languages, each serving a distinct purpose:

Sub-langu
Stands For Purpose Key Commands
age

CREATE, ALTER,
DDL Data Definition Language Define & modify database structure
DROP, TRUNCATE

INSERT, UPDATE,
DML Data Manipulation Language Insert, update, delete data
DELETE, MERGE

DQL Data Query Language Retrieve data SELECT

DCL Data Control Language Manage permissions & access GRANT, REVOKE

Transaction Control COMMIT, ROLLBACK,


TCL Control transactions
Language SAVEPOINT

DDL — Creating & Modifying Structures

-- Create a new table

CREATE TABLE products (

product_id INT PRIMARY KEY AUTO_INCREMENT,

product_name VARCHAR(200) NOT NULL,

category VARCHAR(100),

price DECIMAL(10,2) NOT NULL,

stock_qty INT DEFAULT 0

);

-- Add a new column

ALTER TABLE products ADD COLUMN supplier_id INT;

-- Rename a column (PostgreSQL syntax)

ALTER TABLE products RENAME COLUMN category TO product_category;

-- Delete a table permanently

DROP TABLE IF EXISTS products;

-- Remove all rows but keep the structure

TRUNCATE TABLE products;

Mastering SQL — A Complete Guide | Page 5


DML — Inserting, Updating & Deleting Data

-- INSERT single row

INSERT INTO products (product_name, price, stock_qty)

VALUES ('Wireless Keyboard', 49.99, 200);

-- INSERT multiple rows

INSERT INTO products (product_name, price, stock_qty) VALUES

('USB Hub', 24.99, 500),

('Webcam HD', 79.99, 150),

('Monitor Stand', 34.99, 300);

-- UPDATE specific rows

UPDATE products

SET price = 44.99, stock_qty = 180

WHERE product_id = 1;

-- DELETE specific rows

DELETE FROM products

WHERE stock_qty = 0;

PRO TIP: ALWAYS use a WHERE clause with UPDATE and DELETE. Without it, every row in the table is
affected. Test your WHERE condition with a SELECT first before executing the change.

Mastering SQL — A Complete Guide | Page 6


CHAPTER 3

Filtering, Sorting & Aggregation


SELECT, WHERE, GROUP BY, HAVING & ORDER BY

The SELECT Statement — Full Anatomy


Understanding the logical execution order of SELECT is critical. SQL does not execute clauses in the order
you write them:

Execution Order Clause Purpose

1 FROM / JOIN Identify source tables and combine them

2 WHERE Filter rows before grouping

3 GROUP BY Group filtered rows into summary buckets

4 HAVING Filter groups (applied after GROUP BY)

5 SELECT Choose and compute columns to output

6 DISTINCT Remove duplicate rows from the output

7 ORDER BY Sort the final result set

8 LIMIT / OFFSET Restrict how many rows are returned

WHERE Clause — Filtering Operators

-- Comparison operators

SELECT * FROM products WHERE price > 50;

SELECT * FROM products WHERE price BETWEEN 20 AND 80;

-- Pattern matching with LIKE

SELECT * FROM products WHERE product_name LIKE 'Wire%'; -- starts with Wire

SELECT * FROM products WHERE product_name LIKE '%board'; -- ends with board

SELECT * FROM products WHERE product_name LIKE '%key%'; -- contains key

-- IN — match any value in a list

SELECT * FROM products WHERE category IN ('Electronics', 'Accessories');

-- IS NULL check

SELECT * FROM products WHERE supplier_id IS NULL;

-- Combining conditions

Mastering SQL — A Complete Guide | Page 7


SELECT * FROM products

WHERE category = 'Electronics' AND price < 100 AND stock_qty > 0;

Aggregate Functions
Aggregate functions collapse multiple rows into a single summary value. They are almost always used with
GROUP BY to compute per-group statistics.

Function Returns Example

COUNT(*) Number of rows in the group COUNT(*) AS total_orders

COUNT(col) Non-NULL values in column COUNT(email) AS with_email

SUM(col) Total of numeric column SUM(price * qty) AS revenue

AVG(col) Arithmetic mean (NULLs excluded) AVG(salary) AS avg_salary

MIN(col) Smallest value MIN(price) AS cheapest

MAX(col) Largest value MAX(created_at) AS latest

GROUP_CONCA
Concatenates values (MySQL) GROUP_CONCAT(tag) AS tags
T

STRING_AGG Concatenates values (PostgreSQL) STRING_AGG(tag, ', ')

-- Sales report: revenue by category, only categories with > 5 products

SELECT

category,

COUNT(*) AS total_products,

SUM(price * stock_qty) AS inventory_value,

ROUND(AVG(price), 2) AS avg_price,

MAX(price) AS most_expensive

FROM products

WHERE stock_qty > 0 -- filter rows BEFORE grouping

GROUP BY category

HAVING COUNT(*) > 5 -- filter AFTER grouping

ORDER BY inventory_value DESC

LIMIT 10;

Mastering SQL — A Complete Guide | Page 8


CHAPTER 4

Joins — Combining Tables


INNER, LEFT, RIGHT, FULL OUTER, CROSS & SELF Joins

Joins are the heart of relational databases. They let you query data spread across multiple tables as if it were
a single result set. Mastering joins is the single most important SQL skill for analysts and developers alike.

Join Type Returns Use When

You want records that exist in


INNER JOIN Only rows with matching values in BOTH tables
both

All rows from LEFT table + matching rows from right Keep all left records, even
LEFT JOIN
(NULLs where no match) unmatched

Keep all right records (rare —


RIGHT JOIN All rows from RIGHT table + matching rows from left
use LEFT JOIN instead)

Full reconciliation, finding


FULL OUTER JOIN All rows from BOTH tables (NULLs where no match)
mismatches

Cartesian product — every row paired with every


CROSS JOIN Generating combinations
other

Hierarchies, manager-employee,
SELF JOIN Join a table to itself using aliases
adjacency lists

INNER JOIN — The Most Common Join

-- Get all orders with customer and product details

SELECT

o.order_id,

c.full_name AS customer,

p.product_name,

[Link],

oi.unit_price,

([Link] * oi.unit_price) AS line_total

FROM orders o

INNER JOIN customers c ON o.customer_id = c.customer_id

INNER JOIN order_items oi ON o.order_id = oi.order_id

INNER JOIN products p ON oi.product_id = p.product_id

WHERE o.order_date >= '2024-01-01'

ORDER BY o.order_date DESC;

Mastering SQL — A Complete Guide | Page 9


LEFT JOIN — Finding Missing Records

-- Find customers who have NEVER placed an order

SELECT

c.customer_id,

c.full_name,

[Link],

o.order_id -- will be NULL for customers with no orders

FROM customers c

LEFT JOIN orders o ON c.customer_id = o.customer_id

WHERE o.order_id IS NULL; -- filter for unmatched rows only

SELF JOIN — Hierarchical Data

-- Show each employee with their manager's name

SELECT

e.employee_id,

e.full_name AS employee,

m.full_name AS manager

FROM employees e

LEFT JOIN employees m ON e.manager_id = m.employee_id

ORDER BY m.full_name, e.full_name;

PRO TIP: When joining more than 2 tables, always alias your tables with short, meaningful names (o =
orders, c = customers). This makes complex queries readable and reduces mistakes.

Mastering SQL — A Complete Guide | Page 10


CHAPTER 5

Subqueries & CTEs


Scalar, Correlated, IN/EXISTS Subqueries & Common Table Expressions

Subqueries — Queries Within Queries


A subquery is a SELECT statement nested inside another SQL statement. They can appear in the SELECT
list, FROM clause, WHERE clause, or HAVING clause.

-- Scalar subquery (returns a single value)

SELECT product_name, price,

price - (SELECT AVG(price) FROM products) AS diff_from_avg

FROM products

ORDER BY diff_from_avg DESC;

-- Subquery in WHERE with IN

SELECT full_name, email

FROM customers

WHERE customer_id IN (

SELECT DISTINCT customer_id

FROM orders

WHERE order_date >= '2024-06-01'

);

-- Correlated subquery (executes once per outer row)

SELECT product_name, price, category

FROM products p

WHERE price = (

SELECT MAX(price)

FROM products

WHERE category = [Link] -- references outer query

);

EXISTS vs IN — Performance Matters


EXISTS stops scanning as soon as it finds one matching row, making it faster than IN for large datasets
where subquery results are big.

-- Using EXISTS (often faster for large tables)

Mastering SQL — A Complete Guide | Page 11


SELECT c.full_name

FROM customers c

WHERE EXISTS (

SELECT 1 FROM orders o

WHERE o.customer_id = c.customer_id

AND o.total_amount > 1000

);

Common Table Expressions (CTEs)


CTEs (introduced with the WITH keyword) create named temporary result sets that exist only for the duration
of the query. They dramatically improve readability by breaking complex queries into logical, named steps —
like functions for SQL.

-- Multi-step CTE: monthly revenue trend with running total

WITH monthly_sales AS (

SELECT

DATE_FORMAT(order_date, '%Y-%m') AS month,

SUM(total_amount) AS revenue

FROM orders

WHERE order_date >= '2024-01-01'

GROUP BY DATE_FORMAT(order_date, '%Y-%m')

),

ranked_months AS (

SELECT

month,

revenue,

SUM(revenue) OVER (ORDER BY month) AS running_total,

RANK() OVER (ORDER BY revenue DESC) AS revenue_rank

FROM monthly_sales

SELECT * FROM ranked_months

ORDER BY month;

PRO TIP: Recursive CTEs can traverse hierarchical data (org charts, file systems, bill of materials). Use
WITH RECURSIVE in PostgreSQL/MySQL 8+ for tree traversal queries.

Mastering SQL — A Complete Guide | Page 12


CHAPTER 6

Window Functions
OVER(), PARTITION BY, ROW_NUMBER, RANK, LAG, LEAD & More

Window functions perform calculations across a set of rows related to the current row WITHOUT collapsing
them into a single result (unlike GROUP BY). They are one of the most powerful and interview-tested
features in modern SQL.

Function Category What It Does

ROW_NUMBER() Ranking Sequential integer — no ties

RANK() Ranking Rank with gaps on ties (1,1,3,4)

DENSE_RANK() Ranking Rank without gaps on ties (1,1,2,3)

NTILE(n) Ranking Divides rows into n equal buckets

LAG(col, n) Navigation Value from n rows BEFORE current row

LEAD(col, n) Navigation Value from n rows AFTER current row

FIRST_VALUE(col) Navigation First value in the window frame

LAST_VALUE(col) Navigation Last value in the window frame

SUM() OVER() Aggregation Running or partitioned sum

AVG() OVER() Aggregation Running or partitioned average

COUNT() OVER() Aggregation Running count within partition

-- Ranking employees by salary within each department

SELECT

full_name,

department,

salary,

RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank,

DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_dense_rank,

ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_num

FROM employees;

-- Month-over-month revenue change using LAG

SELECT

month,

revenue,

Mastering SQL — A Complete Guide | Page 13


LAG(revenue, 1) OVER (ORDER BY month) AS prev_month_revenue,

revenue - LAG(revenue, 1) OVER (ORDER BY month) AS mom_change,

ROUND(

(revenue - LAG(revenue,1) OVER (ORDER BY month))

/ LAG(revenue,1) OVER (ORDER BY month) * 100, 2

) AS pct_change

FROM monthly_sales;

PRO TIP: To get the TOP N rows per group, use ROW_NUMBER() inside a CTE and filter WHERE rn <= N
in the outer query. This is one of the most common interview patterns.

Mastering SQL — A Complete Guide | Page 14


CHAPTER 7

Indexes & Query Performance


B-Tree Indexes, Composite Indexes, EXPLAIN & Query Optimisation

What is an Index?
An index is a separate data structure (usually a B-Tree) that the database maintains alongside your table to
enable fast lookups. Without an index, the database performs a full table scan — reading every row. With
an index, it can jump directly to the relevant rows. Indexes speed up reads but slow down writes
(INSERT/UPDATE/DELETE).

Index Type Best For Notes

B-Tree (default) Range queries, equality, ORDER BY Default type in all major databases

Hash Index Exact equality lookups only Faster for = but useless for ranges

Composite Index Queries filtering on multiple columns Column order matters — leftmost prefix rule

Unique Index Enforcing uniqueness + fast lookups Created automatically for UNIQUE constraints

Full-Text Index LIKE '%word%' text searches Use MATCH() ... AGAINST() with this type

Partial Index Indexing a subset of rows e.g. WHERE is_active = TRUE (PostgreSQL)

All columns in SELECT + WHERE are in the


Covering Index Query satisfied entirely from the index
index

-- Single column index

CREATE INDEX idx_orders_customer ON orders(customer_id);

-- Composite index (order matters: filter by status first, then date)

CREATE INDEX idx_orders_status_date ON orders(status, order_date);

-- Unique index

CREATE UNIQUE INDEX idx_users_email ON users(email);

-- Drop an index

DROP INDEX idx_orders_customer ON orders; -- MySQL

DROP INDEX idx_orders_customer; -- PostgreSQL

-- Analyse query execution plan

EXPLAIN SELECT * FROM orders WHERE customer_id = 42 AND status = 'shipped';

Query Optimisation — Key Principles

Mastering SQL — A Complete Guide | Page 15


• Use indexes on WHERE, JOIN ON, and ORDER BY columns — these are the most common
bottlenecks.
• Avoid functions on indexed columns in WHERE — WHERE YEAR(order_date) = 2024 prevents
index use. Use WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31' instead.
• SELECT only what you need — avoid SELECT *. It transfers unnecessary data and prevents covering
index optimisations.
• Avoid correlated subqueries in large tables — they re-execute for every row. Rewrite with JOINs or
CTEs.
• Use LIMIT — when you need only a few rows, LIMIT 10 lets the engine stop early.
• Check EXPLAIN output — look for 'Using filesort', 'Using temporary', and 'type: ALL' — these signal
performance problems.

Mastering SQL — A Complete Guide | Page 16


CHAPTER 8

Stored Procedures, Functions & Triggers


Reusable SQL Logic, Automation & Event-Driven Actions

Stored Procedures
A stored procedure is a named, precompiled block of SQL stored in the database. It can accept parameters,
contain conditional logic, loops, and error handling — essentially a program written in SQL's procedural
extension (PL/SQL, T-SQL, PL/pgSQL).

-- MySQL: Stored procedure to transfer funds between accounts

DELIMITER $$

CREATE PROCEDURE transfer_funds(

IN from_account INT,

IN to_account INT,

IN amount DECIMAL(12,2),

OUT result VARCHAR(50)

BEGIN

DECLARE current_balance DECIMAL(12,2);

SELECT balance INTO current_balance FROM accounts WHERE id = from_account;

IF current_balance < amount THEN

SET result = 'INSUFFICIENT_FUNDS';

ELSE

UPDATE accounts SET balance = balance - amount WHERE id = from_account;

UPDATE accounts SET balance = balance + amount WHERE id = to_account;

SET result = 'SUCCESS';

END IF;

END $$

DELIMITER ;

-- Call the procedure

CALL transfer_funds(101, 202, 500.00, @result);

SELECT @result;

Triggers — Automatic Actions on Data Events

Mastering SQL — A Complete Guide | Page 17


A trigger fires automatically BEFORE or AFTER an INSERT, UPDATE, or DELETE on a table. They are
used for audit logging, enforcing complex business rules, and maintaining derived data.

-- Audit trigger: log every salary change

CREATE TRIGGER trg_salary_audit

AFTER UPDATE ON employees

FOR EACH ROW

BEGIN

IF [Link] <> [Link] THEN

INSERT INTO salary_audit_log (employee_id, old_salary, new_salary, changed_at)

VALUES (OLD.employee_id, [Link], [Link], NOW());

END IF;

END;

Mastering SQL — A Complete Guide | Page 18


CHAPTER 9

Transactions & Concurrency


ACID Properties, Isolation Levels & Locking

ACID Properties
Every database transaction must satisfy four properties to guarantee data reliability:

Property Meaning Example

All operations in a transaction succeed or all Bank transfer: debit AND credit must both
Atomicity
fail — no partial commits succeed

A transaction brings the database from one Account balance cannot go negative if
Consistency
valid state to another constrained

Concurrent transactions do not interfere with Two users booking the last seat see each
Isolation
each other other's changes correctly

Once committed, changes survive crashes Committed orders persist even after server
Durability
and power failures restart

-- Transaction example: safe order placement

START TRANSACTION;

-- Step 1: Insert order

INSERT INTO orders (customer_id, total_amount) VALUES (42, 299.99);

SET @new_order_id = LAST_INSERT_ID();

-- Step 2: Reduce stock

UPDATE products SET stock_qty = stock_qty - 1 WHERE product_id = 7;

-- Step 3: Check stock didn't go negative

IF (SELECT stock_qty FROM products WHERE product_id = 7) < 0 THEN

ROLLBACK; -- undo everything

ELSE

COMMIT; -- persist everything

END IF;

Transaction Isolation Levels

Isolation Level Dirty Read Non-repeatable Read Phantom Read

READ UNCOMMITTED Possible Possible Possible

Mastering SQL — A Complete Guide | Page 19


READ COMMITTED Prevented Possible Possible

REPEATABLE READ Prevented Prevented Possible

SERIALIZABLE Prevented Prevented Prevented

Mastering SQL — A Complete Guide | Page 20


CHAPTER 10

Interview Questions & Practice Problems


Frequently Asked SQL Questions + Hands-On Practice Exercises

Top Interview Questions

Q1: What is the difference between WHERE and HAVING?

WHERE filters individual rows BEFORE grouping; HAVING filters groups AFTER GROUP BY. WHERE
cannot reference aggregate functions (SUM, COUNT etc.) but HAVING can.

Q2: What is the difference between DELETE, TRUNCATE and DROP?

DELETE removes specific rows and can be rolled back (DML). TRUNCATE removes ALL rows faster and
cannot always be rolled back (DDL in MySQL). DROP removes the entire table structure and data
permanently.

Q3: What is a correlated subquery?

A subquery that references columns from the outer query. It re-executes for every row processed by the
outer query, making it potentially slow on large tables. Can often be rewritten as a JOIN for better
performance.

Q4: Explain the difference between RANK(), DENSE_RANK() and ROW_NUMBER().

All three assign a position to rows. ROW_NUMBER gives a unique sequential number (no ties). RANK
gives the same number to ties but leaves gaps (1,1,3). DENSE_RANK gives the same number to ties
without gaps (1,1,2).

Q5: What is a covering index?

An index that contains all columns needed by a query (in SELECT, WHERE, and ORDER BY). The
database can satisfy the query entirely from the index without touching the main table, making it extremely
fast.

Q6: How do you find duplicate rows in a table?

Use GROUP BY on the duplicate key columns and HAVING COUNT(*) > 1. Example: SELECT email,
COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1.

Q7: What is normalisation? Name the normal forms.

Mastering SQL — A Complete Guide | Page 21


Normalisation is the process of structuring tables to reduce redundancy and dependency. 1NF: atomic
values, no repeating groups. 2NF: no partial dependency on composite key. 3NF: no transitive
dependency. BCNF: every determinant is a candidate key.

Q8: What is the N+1 query problem?

In application code, fetching a list of N records and then running 1 additional query per record = N+1 total
queries. Solved by using JOINs or eager loading to fetch all needed data in a single query.

Practice Problems — Beginner


Use the following schema for all practice problems: customers(id, name, email, city) | orders(id,
customer_id, amount, status, order_date) | products(id, name, category, price, stock) |
order_items(order_id, product_id, quantity, unit_price)

Q List all customers from Mumbai, sorted alphabetically by name.


1.
Hint: Use WHERE city = 'Mumbai' and ORDER BY name.

Answer: SELECT name, email FROM customers WHERE city = 'Mumbai' ORDER BY name;

Q Find all products with price between Rs.500 and Rs.2000.


2.
Hint: Use BETWEEN operator.

Answer: SELECT name, price FROM products WHERE price BETWEEN 500 AND 2000;

Q Count the total number of orders placed in 2024.


3.
Hint: Use COUNT(*) with a WHERE on order_date.

Answer: SELECT COUNT(*) FROM orders WHERE YEAR(order_date) = 2024;

Q Find the top 5 most expensive products.


4.
Hint: Use ORDER BY price DESC LIMIT 5.

Answer: SELECT name, price FROM products ORDER BY price DESC LIMIT 5;

Q Find all orders with status 'pending' or 'processing'.


5.
Hint: Use IN operator.

Answer: SELECT * FROM orders WHERE status IN ('pending', 'processing');

Practice Problems — Intermediate

Q For each customer, show their name and total amount spent across all orders. Only include customers who
1. have spent more than Rs.10,000.
Hint: JOIN customers to orders, GROUP BY customer, use HAVING.

Answer: SELECT [Link], SUM([Link]) AS total FROM customers c JOIN orders o ON [Link] = o.customer_id
GROUP BY [Link], [Link] HAVING SUM([Link]) > 10000 ORDER BY total DESC;

Mastering SQL — A Complete Guide | Page 22


Q Find customers who have placed orders but never bought a product from the 'Electronics' category.
2.
Hint: Use NOT EXISTS or NOT IN with a subquery joining order_items and products.

Answer: SELECT DISTINCT [Link] FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE
o.customer_id = [Link]) AND [Link] NOT IN (SELECT o.customer_id FROM orders o JOIN order_items oi ON [Link] =
oi.order_id JOIN products p ON oi.product_id = [Link] WHERE [Link] = 'Electronics');

Q Show each product's name, its category, its price, and the average price of all products in the same
3. category.
Hint: Use AVG() as a window function with PARTITION BY category.

Answer: SELECT name, category, price, ROUND(AVG(price) OVER (PARTITION BY category), 2) AS


category_avg FROM products;

Q List the top 3 best-selling products by total quantity sold.


4.
Hint: JOIN order_items to products, GROUP BY product, ORDER BY SUM(quantity) DESC LIMIT 3.

Answer: SELECT [Link], SUM([Link]) AS qty_sold FROM products p JOIN order_items oi ON [Link] =
oi.product_id GROUP BY [Link], [Link] ORDER BY qty_sold DESC LIMIT 3;

Practice Problems — Advanced

Q For each month in 2024, show total revenue, the previous month's revenue, and the percentage change
1. month-over-month.
Hint: Use a CTE for monthly revenue, then LAG() window function for previous month.

Answer: WITH m AS (SELECT DATE_FORMAT(order_date,'%Y-%m') mo, SUM(amount) rev FROM orders


WHERE YEAR(order_date)=2024 GROUP BY mo) SELECT mo, rev, LAG(rev) OVER (ORDER BY mo) prev,
ROUND((rev - LAG(rev) OVER (ORDER BY mo)) / LAG(rev) OVER (ORDER BY mo) * 100, 2) pct FROM m
ORDER BY mo;

Q Find all customers who placed an order every single month in 2024 (12 consecutive months).
2.
Hint: COUNT(DISTINCT month) = 12 per customer.

Answer: SELECT customer_id FROM orders WHERE YEAR(order_date) = 2024 GROUP BY customer_id
HAVING COUNT(DISTINCT MONTH(order_date)) = 12;

Q For each product, show the running total of units sold over time (cumulative sum ordered by order date).
3.
Hint: Use SUM() as a window function with ORDER BY inside OVER().

Answer: SELECT [Link], o.order_date, [Link], SUM([Link]) OVER (PARTITION BY oi.product_id


ORDER BY o.order_date) AS running_total FROM order_items oi JOIN orders o ON oi.order_id = [Link] JOIN
products p ON oi.product_id = [Link] ORDER BY [Link], o.order_date;

Mastering SQL — A Complete Guide | Page 23


CHAPTER

SQL Quick Reference Cheatsheet

SELECT Skeleton
SELECT col1, col2, AGG_FUNC(col3)

FROM table_name

JOIN other ON table_name.id = [Link]

WHERE condition

GROUP BY col1, col2

HAVING AGG_FUNC(col3) > value

ORDER BY col1 ASC, col2 DESC

LIMIT n OFFSET m;

Window Function Skeleton


SELECT col,

FUNCTION() OVER (

PARTITION BY partition_col

ORDER BY order_col

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

) AS alias

FROM table;

CTE Skeleton
WITH cte_name AS (

SELECT ... FROM ... WHERE ...

),

cte_two AS (

SELECT ... FROM cte_name WHERE ...

SELECT * FROM cte_two;

Find Duplicates
SELECT col, COUNT(*)

FROM table

GROUP BY col

Mastering SQL — A Complete Guide | Page 24


HAVING COUNT(*) > 1;

Delete Duplicates (keep lowest id)


DELETE FROM table

WHERE id NOT IN (

SELECT MIN(id)

FROM table

GROUP BY duplicate_col

);

Top N Per Group


WITH ranked AS (

SELECT *, ROW_NUMBER() OVER

(PARTITION BY group_col ORDER BY rank_col DESC) rn

FROM table

SELECT * FROM ranked WHERE rn <= N;

Keep practising. SQL mastery is built query by query. Every complex problem you
solve makes the next one easier.

Mastering SQL — A Complete Guide | Page 25

You might also like