SQL Practice Exercises — 40 Hands-On Problems
All exercises use the consistent employees, departments, jobs, job_history, salaries, and transactions tables. Each exercise shows the problem, the SQL solution, and the learning outcome. All answers are collapsed by default — click the header to reveal.
Basic SQL Exercises (1-8)
Master the fundamentals — SELECT, filtering, sorting, and aggregation. Click each exercise to reveal the solution (collapsed by default).
Select All Columns
Beginner SELECTProblem: Retrieve all columns from the employees table.
SELECT *
FROM employees;
Select Specific Columns
Beginner SELECTProblem: Retrieve the first_name and last_name columns from the employees table.
SELECT first_name, last_name
FROM employees;
Filter Rows Using WHERE
Beginner WHEREProblem: Retrieve employees whose age is greater than 30.
SELECT *
FROM employees
WHERE age > 30;
Sort Data Using ORDER BY
Beginner ORDER BYProblem: Retrieve all employees sorted by last_name in ascending order.
SELECT *
FROM employees
ORDER BY last_name ASC;
Limit the Number of Rows
Beginner LIMITProblem: Retrieve the first 5 employees from the table.
SELECT *
FROM employees
LIMIT 5;
Calculate Average Salary
Beginner Aggregate FunctionsProblem: Calculate the average salary from the salaries table.
SELECT AVG(salary) AS average_salary
FROM salaries;
AVG() aggregate function.Average Salary by Department
Beginner GROUP BYProblem: Calculate the average salary for each department.
SELECT
department_id,
AVG(salary) AS average_salary
FROM salaries
GROUP BY department_id;
GROUP BY works with aggregate functions.Filter Groups Using HAVING
Beginner → Intermediate HAVINGProblem: Retrieve departments whose average salary is greater than 50,000.
SELECT
department_id,
AVG(salary) AS average_salary
FROM salaries
GROUP BY department_id
HAVING AVG(salary) > 50000;
WHERE and HAVING.Intermediate SQL Exercises (9-20)
Practice JOINs, subqueries, set operations, and conditional logic with real employees/departments data.
INNER JOIN
Intermediate JOINProblem: Retrieve employees along with their department names.
SELECT
e.first_name,
e.last_name,
d.department_name
FROM employees e
INNER JOIN departments d
ON e.department_id = d.department_id;
LEFT JOIN
Intermediate JOINProblem: Retrieve all employees and their department names, including employees who do not belong to a department.
SELECT
e.first_name,
e.last_name,
d.department_name
FROM employees e
LEFT JOIN departments d
ON e.department_id = d.department_id;
LEFT JOIN preserves all rows from the left table.RIGHT JOIN
Intermediate JOINProblem: Retrieve all departments and their employees, including departments that have no employees.
SELECT
e.first_name,
e.last_name,
d.department_name
FROM employees e
RIGHT JOIN departments d
ON e.department_id = d.department_id;
RIGHT JOIN preserves all rows from the right table.FULL OUTER JOIN
Intermediate JOINProblem: Retrieve all employees and all departments, including records that do not have a matching record.
SELECT
e.first_name,
e.last_name,
d.department_name
FROM employees e
FULL OUTER JOIN departments d
ON e.department_id = d.department_id;
FULL OUTER JOIN returns matched and unmatched records from both tables.SELF JOIN
Intermediate SELF JOINProblem: Retrieve each employee along with their manager's name.
SELECT
e.first_name AS employee_name,
m.first_name AS manager_name
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.employee_id;
Subquery
Intermediate SubqueriesProblem: Retrieve employees whose salary is greater than the overall average salary.
SELECT
first_name,
last_name,
salary
FROM employees
WHERE salary > (
SELECT AVG(salary)
FROM employees
);
WHERE condition.EXISTS
Intermediate EXISTSProblem: Retrieve departments that have at least one employee.
SELECT
d.department_id,
d.department_name
FROM departments d
WHERE EXISTS (
SELECT 1
FROM employees e
WHERE e.department_id = d.department_id
);
EXISTS checks whether a related record exists.IN
Intermediate INProblem: Retrieve employees who belong to departments 1, 2, or 3.
SELECT
first_name,
last_name,
department_id
FROM employees
WHERE department_id IN (1, 2, 3);
UNION
Intermediate Set OperationsProblem: Retrieve all unique job titles from jobs and job_history.
SELECT job_title
FROM jobs
UNION
SELECT job_title
FROM job_history;
UNION combines result sets and removes duplicates.UNION ALL
Intermediate Set OperationsProblem: Retrieve all job titles from jobs and job_history, including duplicates.
SELECT job_title
FROM jobs
UNION ALL
SELECT job_title
FROM job_history;
UNION and UNION ALL.CASE Statement
Intermediate Conditional LogicProblem: Display whether each employee's salary is above or below the overall average salary.
SELECT
first_name,
last_name,
salary,
CASE
WHEN salary > (
SELECT AVG(salary)
FROM employees
)
THEN 'Above Average'
ELSE 'Below Average'
END AS salary_comparison
FROM employees;
CASE.COALESCE
Intermediate NULL HandlingProblem: Display each employee's manager name. If the employee does not have a manager, display No Manager.
SELECT
e.first_name,
e.last_name,
COALESCE(m.first_name, 'No Manager') AS manager_name
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.employee_id;
NULL values using COALESCE().Advanced SQL Exercises (21-40)
Window functions, CTEs, PIVOT/MERGE, views, procedures, and Data Engineer interview patterns. Click to expand each solution.
ROW_NUMBER
Advanced Window FunctionsProblem: Assign a unique row number to each employee within their department.
SELECT
first_name,
last_name,
department_id,
ROW_NUMBER() OVER (
PARTITION BY department_id
ORDER BY first_name
) AS row_num
FROM employees;
ROW_NUMBER() and PARTITION BY.RANK
Advanced Window FunctionsProblem: Rank employees within each department based on salary, from highest to lowest.
SELECT
first_name,
last_name,
department_id,
salary,
RANK() OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS salary_rank
FROM employees;
DENSE_RANK
Advanced Window FunctionsProblem: Rank employees within each department based on salary using dense ranking.
SELECT
first_name,
last_name,
department_id,
salary,
DENSE_RANK() OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS dense_rank
FROM employees;
RANK() and DENSE_RANK().NTILE
Advanced Window FunctionsProblem: Divide employees into 4 salary groups within each department.
SELECT
first_name,
last_name,
department_id,
salary,
NTILE(4) OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS quartile
FROM employees;
NTILE() divides rows into groups.LEAD
Advanced Window FunctionsProblem: Retrieve the next employee's salary within each department.
SELECT
first_name,
last_name,
department_id,
salary,
LEAD(salary) OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS next_salary
FROM employees;
LAG
Advanced Window FunctionsProblem: Retrieve the previous employee's salary within each department.
SELECT
first_name,
last_name,
department_id,
salary,
LAG(salary) OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS previous_salary
FROM employees;
FIRST_VALUE
Advanced Window FunctionsProblem: Retrieve the highest salary within each department.
SELECT
first_name,
last_name,
department_id,
salary,
FIRST_VALUE(salary) OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS first_salary
FROM employees;
FIRST_VALUE() works with window ordering.LAST_VALUE
Advanced Window FunctionsProblem: Retrieve the last salary according to the specified window ordering within each department.
SELECT
first_name,
last_name,
department_id,
salary,
LAST_VALUE(salary) OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS last_salary
FROM employees;
LAST_VALUE() and window frames. Note: behavior can depend on window frame.Common Table Expression
Advanced CTEProblem: Calculate the average salary for each department and display it alongside each employee.
WITH AvgSalaries AS (
SELECT
department_id,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
)
SELECT
e.first_name,
e.last_name,
e.salary,
a.avg_salary
FROM employees e
JOIN AvgSalaries a
ON e.department_id = a.department_id;
Recursive CTE
Advanced Recursive CTEProblem: Generate an employee hierarchy starting from employees who do not have a manager.
WITH RECURSIVE EmployeeHierarchy AS (
SELECT
employee_id,
first_name,
last_name,
manager_id,
0 AS level
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT
e.employee_id,
e.first_name,
e.last_name,
e.manager_id,
eh.level + 1
FROM employees e
JOIN EmployeeHierarchy eh
ON e.manager_id = eh.employee_id
)
SELECT *
FROM EmployeeHierarchy;
PIVOT
Advanced PIVOTProblem: Generate a report showing total sales by product category and month.
SELECT *
FROM (
SELECT
product_category,
MONTH(order_date) AS month,
SUM(sales_amount) AS total_sales
FROM sales
GROUP BY
product_category,
MONTH(order_date)
) AS PivotTable
PIVOT (
SUM(total_sales)
FOR month IN (
1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12
)
) AS MonthlySales;
UNPIVOT
Advanced UNPIVOTProblem: Convert monthly columns back into rows.
SELECT
product_id,
month,
sales_amount
FROM MonthlySales
UNPIVOT (
sales_amount
FOR month IN (
1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12
)
) AS UnpivotedSales;
MERGE
Advanced MERGE / UpsertProblem: Update existing records and insert new records from a staging table into a target table.
MERGE INTO target_table AS T
USING staging_table AS S
ON T.id = S.id
WHEN MATCHED THEN
UPDATE SET
T.col1 = S.col1,
T.col2 = S.col2
WHEN NOT MATCHED THEN
INSERT (id, col1, col2)
VALUES (S.id, S.col1, S.col2);
UPDATE with a Condition
Intermediate DMLProblem: Increase salary by 10% for employees with more than 5 years of service.
UPDATE employees
SET salary = salary * 1.10
WHERE years_of_service > 5;
INSERT INTO SELECT
Advanced DMLProblem: Insert employee records from a temporary table into the main employees table.
INSERT INTO employees (
employee_id,
first_name,
last_name,
department_id
)
SELECT
employee_id,
first_name,
last_name,
department_id
FROM temp_employees;
Create a View
Advanced ViewsProblem: Create a view containing employee and department information.
CREATE VIEW employee_details AS
SELECT
e.employee_id,
e.first_name,
e.last_name,
d.department_name
FROM employees e
JOIN departments d
ON e.department_id = d.department_id;
Query a View
Advanced ViewsProblem: Retrieve data from the employee_details view.
SELECT *
FROM employee_details;
Create a Stored Procedure
Advanced Stored ProceduresProblem: Create a stored procedure that calculates a 10% bonus for an employee. (MySQL-style)
DELIMITER //
CREATE PROCEDURE calculate_bonus(IN emp_id INT)
BEGIN
DECLARE bonus_amount DECIMAL(10,2);
SELECT salary * 0.10
INTO bonus_amount
FROM employees
WHERE employee_id = emp_id;
UPDATE employees
SET bonus = bonus_amount
WHERE employee_id = emp_id;
END //
DELIMITER ;
Call a Stored Procedure
Advanced Stored ProceduresProblem: Execute the calculate_bonus procedure for employee 1001.
CALL calculate_bonus(1001);
Exception Handling in Stored Procedures
Advanced Stored ProceduresProblem: Create a stored procedure that handles an error while inserting an employee. (MySQL-style)
DELIMITER //
CREATE PROCEDURE insert_employee (
IN emp_id INT,
IN emp_name VARCHAR(255),
IN dept_id INT
)
BEGIN
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 'Error: Unable to insert employee.';
END;
START TRANSACTION;
INSERT INTO employees (
employee_id,
employee_name,
department_id
)
VALUES (
emp_id,
emp_name,
dept_id
);
COMMIT;
END //
DELIMITER ;