Last Updated on: 30th March 2026, 01:12 pm
If you are learning SQL and still feel confused solving questions, then honestly you are not alone. Most people read theory, but when real query comes… mind just goes blank little bit.
Why Practice Matters
SQL is not something you “memorize”. It’s something you practice, mess up, fix, repeat again. If you only read SELECT, WHERE, JOIN — you think you understand… but when a real question comes, confusion starts. The only way is solving questions.
Database Setup
These three tables are used across all 100+ questions below.
| Table | Columns |
|---|---|
| Students | id, name, marks, city, dept_id |
| Departments | id, dept_name |
| Employees | id, name, salary, dept_id |
Section 1 — Basic SQL Questions (1–25)
SELECT, WHERE, ORDER BY, Aggregates
Start simple. Build confidence first.
#1Get all records from Students
SELECT * FROM Students;
📖 Why? The asterisk
* means “all columns”. Returns every row and column. Perfect for initial data inspection.#2Get only student names
SELECT name FROM Students;
📖 Column projection: select only the
name column instead of everything.#3Students with marks greater than 80
SELECT * FROM Students WHERE marks > 80;
📖 WHERE clause filters rows. Operators:
>, <, =, >=, <=.#4Get students from Lahore
SELECT * FROM Students WHERE city = 'Lahore';
📖 String filtering: use single quotes for text values.
#5Students between 60 and 90 marks
SELECT * FROM Students WHERE marks BETWEEN 60 AND 90;
📖 BETWEEN is inclusive shorthand for
marks >= 60 AND marks <= 90.#6Names starting with ‘A’
SELECT * FROM Students WHERE name LIKE 'A%';
📖
% matches any sequence of characters. 'A%' = names beginning with A.#7Names ending with ‘n’
SELECT * FROM Students WHERE name LIKE '%n';
📖
'%n' → any characters before the final letter ‘n’.#8Sort students by marks ascending
SELECT * FROM Students ORDER BY marks;
📖 ORDER BY default is ascending (lowest to highest).
#9Sort students by marks descending
SELECT * FROM Students ORDER BY marks DESC;
📖
DESC → highest marks first.#10Count total students
SELECT COUNT(*) FROM Students;
📖
COUNT(*) returns total number of rows.#11Find maximum marks
SELECT MAX(marks) FROM Students;
📖
MAX() returns the highest value.#12Find minimum marks
SELECT MIN(marks) FROM Students;
📖
MIN() returns the lowest value.#13Find average marks
SELECT AVG(marks) FROM Students;
📖
AVG() calculates arithmetic mean (ignores NULLs).#14Find total marks (sum)
SELECT SUM(marks) FROM Students;
📖
SUM() adds all numeric values in the column.#15Get distinct cities
SELECT DISTINCT city FROM Students;
📖 DISTINCT removes duplicates → unique city names only.
#16Students not from Karachi
SELECT * FROM Students WHERE city != 'Karachi';
📖
!= (or <>) means “not equal”.#17Students with NULL department
SELECT * FROM Students WHERE dept_id IS NULL;
📖 Always use
IS NULL — never = NULL.#18Students with NOT NULL department
SELECT * FROM Students WHERE dept_id IS NOT NULL;
📖 Keeps rows where department is assigned.
#19Students with marks >= 90
SELECT * FROM Students WHERE marks >= 90;
📖
>= includes the boundary value.#20Students with marks not equal 50
SELECT * FROM Students WHERE marks != 50;
📖 Excludes the exact value 50.
#21Top 5 students (first 5 rows)
SELECT * FROM Students LIMIT 5;
📖
LIMIT restricts rows (MySQL/PostgreSQL). SQL Server uses TOP 5.#22Students sorted by name A–Z
SELECT * FROM Students ORDER BY name;
📖 Alphabetical order on the name column.
#23Count distinct cities
SELECT COUNT(DISTINCT city) FROM Students;
📖 Counts unique non-NULL values.
#24Students with marks less than 40
SELECT * FROM Students WHERE marks < 40;
📖 Low-performing students filter.
#25Students with marks in (50, 60, 70)
SELECT * FROM Students WHERE marks IN (50,60,70);
📖 IN matches any value in the list. Cleaner than multiple OR conditions.
Section 2 — Intermediate (26–60)
GROUP BY, HAVING, Subqueries, String Functions
Things get interesting here.
#26AND condition
SELECT * FROM Students WHERE marks > 70 AND city='Lahore';
📖 AND: both conditions must be true.
#27OR condition
SELECT * FROM Students WHERE marks > 70 OR city='Karachi';
📖 OR: at least one condition must be true.
#28Count students per city
SELECT city, COUNT(*) FROM Students GROUP BY city;
📖 GROUP BY groups rows with the same city, then counts each group.
#29Average marks per city
SELECT city, AVG(marks) FROM Students GROUP BY city;
📖 Average marks calculated per city group.
#30Cities with avg marks > 70
SELECT city, AVG(marks) FROM Students GROUP BY city HAVING AVG(marks) > 70;
📖 HAVING filters groups. WHERE filters rows before grouping.
#31Find duplicate cities
SELECT city, COUNT(*) FROM Students GROUP BY city HAVING COUNT(*) > 1;
📖 Cities that appear more than once in the table.
#32Second highest marks
SELECT MAX(marks) FROM Students WHERE marks < (SELECT MAX(marks) FROM Students);
📖 Inner subquery gets highest, outer finds max below that → second highest.
#33Students above average marks
SELECT * FROM Students WHERE marks > (SELECT AVG(marks) FROM Students);
📖 Subquery returns global average; outer selects above-average students.
#34Students below average marks
SELECT * FROM Students WHERE marks < (SELECT AVG(marks) FROM Students);
📖 Opposite: students scoring below average.
#35Names in uppercase
SELECT UPPER(name) FROM Students;
📖
UPPER() converts text to all caps.#36Names in lowercase
SELECT LOWER(name) FROM Students;
📖
LOWER() useful for case-insensitive comparisons.#37Concatenate name and city
SELECT CONCAT(name,' - ',city) FROM Students;
📖
CONCAT() joins strings → "Ali - Lahore".#38Names longer than 5 characters
SELECT * FROM Students WHERE LENGTH(name) > 5;
📖
LENGTH() returns character count; filter on that.#39Replace 'Lahore' with 'LHR'
SELECT REPLACE(city,'Lahore','LHR') FROM Students;
📖
REPLACE() substitutes substrings without modifying original data.#40First 3 letters of names
SELECT SUBSTRING(name,1,3) FROM Students;
📖
SUBSTRING(name, start, length) extracts a prefix.#41Marks divisible by 5
SELECT * FROM Students WHERE marks % 5 = 0;
📖 Modulo
%: remainder zero = divisible by 5.#42Top 3 highest marks
SELECT * FROM Students ORDER BY marks DESC LIMIT 3;
📖 Sort descending, then pick the first 3 rows.
#43Longest name
SELECT name FROM Students ORDER BY LENGTH(name) DESC LIMIT 1;
📖 Order by character length descending, limit 1 = longest name.
#44Students not in Lahore (alt syntax)
SELECT * FROM Students WHERE city <> 'Lahore';
📖
<> is standard SQL not-equal (same as !=).#45Cities with more than 2 students
SELECT city, COUNT(*) FROM Students GROUP BY city HAVING COUNT(*) > 2;
📖 Group by city, keep only groups where count exceeds 2.
#46Count students per department
SELECT dept_id, COUNT(*) FROM Students GROUP BY dept_id;
📖 Student distribution across departments.
#47Sort by city, then marks descending
SELECT * FROM Students ORDER BY city, marks DESC;
📖 Multi-column sort: alphabetical by city, then highest marks within each city.
#48Complex filter
SELECT * FROM Students WHERE marks > 60 AND city != 'Karachi';
📖 Combines numeric and text exclusion in one condition.
#49Names containing 'a'
SELECT * FROM Students WHERE name LIKE '%a%';
📖 Wildcard before and after → 'a' anywhere in the name.
#50Count students with marks > 60
SELECT COUNT(*) FROM Students WHERE marks > 60;
📖 Conditional count using WHERE before aggregation.
#51Maximum marks per city
SELECT city, MAX(marks) FROM Students GROUP BY city;
📖 Highest score achieved in each city.
#52Minimum marks per city
SELECT city, MIN(marks) FROM Students GROUP BY city;
📖 Lowest score per city.
#53Average marks per department
SELECT dept_id, AVG(marks) FROM Students GROUP BY dept_id;
📖 Department-wise academic average.
#54Student with highest marks
SELECT * FROM Students WHERE marks = (SELECT MAX(marks) FROM Students);
📖 Subquery finds top marks; outer gets the student(s).
#55Student with lowest marks
SELECT * FROM Students WHERE marks = (SELECT MIN(marks) FROM Students);
📖 Same pattern for minimum.
#56Names sorted by length
SELECT name FROM Students ORDER BY LENGTH(name);
📖 Shortest names first.
#57Distinct department IDs
SELECT DISTINCT dept_id FROM Students;
📖 Unique department references in the Students table.
#58Marks between 40 and 60
SELECT * FROM Students WHERE marks BETWEEN 40 AND 60;
📖 Range filter — both boundaries are included.
#59Second character is 'a'
SELECT * FROM Students WHERE name LIKE '_a%';
📖
_ matches exactly one character, then 'a', then anything.#60Sort by marks desc, name asc (tie-break)
SELECT * FROM Students ORDER BY marks DESC, name ASC;
📖 When marks are equal, names are sorted ascending as a tie-breaker.
SQL
JOINs · Window Functions · Ranking
Think → Break down → Solve → Repeat
Section 3 — JOIN Queries (61–90)
Combining Data Across Tables
Most important SQL topic. Learn this well.
#61Inner Join: Students + Departments
SELECT s.name, d.dept_name FROM Students s INNER JOIN Departments d ON s.dept_id = d.id;
📖 INNER JOIN returns only rows matching in both tables. Aliases
s and d shorten the query.#62Left Join (all students, optional dept)
SELECT s.name, d.dept_name FROM Students s LEFT JOIN Departments d ON s.dept_id = d.id;
📖 LEFT JOIN keeps every student even with no matching department (NULL for dept_name).
#63Students without a department (anti-join)
SELECT s.name FROM Students s LEFT JOIN Departments d ON s.dept_id = d.id WHERE d.id IS NULL;
📖 Left join + NULL filter → students with no department mapping.
#64Count students per department name
SELECT d.dept_name, COUNT(*) FROM Students s JOIN Departments d ON s.dept_id = d.id GROUP BY d.dept_name;
📖 Join then group by department name for real counts per department.
#65Employees with department names
SELECT e.name, d.dept_name FROM Employees e JOIN Departments d ON e.dept_id = d.id;
📖 Same join pattern applied to the Employees table.
#66Highest salary per department
SELECT dept_id, MAX(salary) FROM Employees GROUP BY dept_id;
📖 Aggregation per department.
#67Average salary per department
SELECT dept_id, AVG(salary) FROM Employees GROUP BY dept_id;
📖 Department-wise average compensation.
#68Employees above company average salary
SELECT * FROM Employees WHERE salary > (SELECT AVG(salary) FROM Employees);
📖 Subquery returns global average; outer selects higher earners.
#69Department with most employees
SELECT dept_id, COUNT(*) FROM Employees GROUP BY dept_id ORDER BY COUNT(*) DESC LIMIT 1;
📖 Group, sort by count desc, take the top row.
#70Employees with NULL department
SELECT * FROM Employees WHERE dept_id IS NULL;
📖 Employees not assigned to any department.
#71Departments with no employees
SELECT d.dept_name FROM Departments d LEFT JOIN Employees e ON d.id=e.dept_id WHERE e.id IS NULL;
📖 Anti-join: departments that have no employee records.
#72Sort employees by salary descending
SELECT name, salary FROM Employees ORDER BY salary DESC;
📖 Highest paid employees first.
#73Departments with more than 2 employees
SELECT dept_id, COUNT(*) FROM Employees GROUP BY dept_id HAVING COUNT(*) > 2;
📖 HAVING filters groups with count > 2.
#74Employee + department name
SELECT e.name, d.dept_name FROM Employees e JOIN Departments d ON e.dept_id = d.id;
📖 Inner join for employee-department pairs.
#75Students and employees in same dept
SELECT s.name, e.name FROM Students s JOIN Employees e ON s.dept_id = e.dept_id;
📖 Cross-table join on a shared department ID.
#76Distinct department IDs in Employees
SELECT DISTINCT dept_id FROM Employees;
📖 Which departments have at least one employee.
#77Order employees by department
SELECT * FROM Employees ORDER BY dept_id;
📖 Groups employees by department visually.
#78Total employees
SELECT COUNT(*) FROM Employees;
📖 Simple total row count.
#79Average salary
SELECT AVG(salary) FROM Employees;
📖 Company-wide average.
#80Maximum salary
SELECT MAX(salary) FROM Employees;
📖 Highest earner's salary.
#81Minimum salary
SELECT MIN(salary) FROM Employees;
📖 Lowest salary in the company.
#82Salary between 20000 and 50000
SELECT * FROM Employees WHERE salary BETWEEN 20000 AND 50000;
📖 Mid-range salary filter.
#83Names starting with 'S'
SELECT * FROM Employees WHERE name LIKE 'S%';
📖 Employees whose name begins with S.
#84Salary greater than 30000
SELECT * FROM Employees WHERE salary > 30000;
📖 Above-threshold salary filter.
#85Sum of salaries per department
SELECT dept_id, SUM(salary) FROM Employees GROUP BY dept_id;
📖 Total payroll per department.
#86Sort employees by name
SELECT name FROM Employees ORDER BY name;
📖 A–Z alphabetical order.
#87First 10 employees
SELECT * FROM Employees LIMIT 10;
📖 Restrict output to first ten rows.
#88Employees in department 1 or 2
SELECT * FROM Employees WHERE dept_id IN (1,2);
📖 IN operator for multiple possible values.
#89Employees not in department 1 or 2
SELECT * FROM Employees WHERE dept_id NOT IN (1,2);
📖 Exclude specific departments.
#90Count distinct departments with employees
SELECT COUNT(DISTINCT dept_id) FROM Employees;
📖 How many departments have at least one employee.
Section 4 — Advanced (91–105)
Window Functions, Ranking, CTEs
Real interview-level questions start here.
#91Second highest salary
SELECT MAX(salary) FROM Employees WHERE salary < (SELECT MAX(salary) FROM Employees);
📖 Find max, then find max below that → second highest.
#92Third highest salary (OFFSET)
SELECT DISTINCT salary FROM Employees ORDER BY salary DESC LIMIT 1 OFFSET 2;
📖
OFFSET 2 skips the first two distinct values → third highest.#93RANK() window function
SELECT name, salary, RANK() OVER (ORDER BY salary DESC) FROM Employees;
📖 Assigns rank with gaps: 1, 2, 2, 4…
#94DENSE_RANK()
SELECT name, salary, DENSE_RANK() OVER (ORDER BY salary DESC) FROM Employees;
📖 No gaps: 1, 2, 2, 3…
#95ROW_NUMBER()
SELECT name, ROW_NUMBER() OVER () FROM Employees;
📖 Simple sequential numbering across all rows.
#96Running total (cumulative sum)
SELECT salary, SUM(salary) OVER (ORDER BY id) FROM Employees;
📖 Adds salaries progressively based on ID order.
#97Duplicate salaries
SELECT salary, COUNT(*) FROM Employees GROUP BY salary HAVING COUNT(*) > 1;
📖 Identify salary values that appear more than once.
#98Top 3 earners per department
SELECT * FROM ( SELECT e.*, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) rn FROM Employees e ) t WHERE rn <= 3;
📖
PARTITION BY resets ranking per department; outer filter keeps top 3.#99Delete duplicate rows (keep smallest ID)
DELETE FROM Students WHERE id NOT IN (SELECT MIN(id) FROM Students GROUP BY name, city);
📖 Keep one row per (name, city) combination — the smallest ID — delete the rest.
#100Self join — employees with same salary
SELECT a.name, b.name, a.salary FROM Employees a JOIN Employees b ON a.salary = b.salary AND a.id != b.id;
📖 Self-join to find pairs of employees earning the same salary.
#101Recursive CTE (count 1–10)
WITH RECURSIVE nums(n) AS ( SELECT 1 UNION ALL SELECT n+1 FROM nums WHERE n < 10 ) SELECT * FROM nums;
📖 Generate a series 1 to 10 using a recursive common table expression.
#102Conditional aggregation (pivot)
SELECT city, SUM(CASE WHEN marks >= 80 THEN 1 ELSE 0 END) AS high_scorers FROM Students GROUP BY city;
📖 Count high-scorers per city using CASE inside SUM.
#103LAG() — previous row salary
SELECT name, salary, LAG(salary) OVER (ORDER BY id) FROM Employees;
📖 Get the previous row's salary — useful for comparison.
#104LEAD() — next row salary
SELECT name, salary, LEAD(salary) OVER (ORDER BY id) FROM Employees;
📖 Get the next row's salary.
#105Moving average (3-row window)
SELECT salary, AVG(salary) OVER (ORDER BY id ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) FROM Employees;
📖 Moving average of the last 3 rows.
How to Approach Any SQL Question
- Step 1 — What columns do I need?
- Step 2 — From which table(s)? Do I need a JOIN?
- Step 3 — Any filters? (WHERE / HAVING)
- Step 4 — Any grouping? (GROUP BY)
- Step 5 — Sorting or limiting? (ORDER BY / LIMIT)
Common Mistakes
- Forgetting GROUP BY when using aggregate functions
- Wrong JOIN condition creating a cartesian product
- Confusing WHERE (row filter) with HAVING (group filter)
- Using
= NULLinstead ofIS NULL
FAQs
How many SQL questions should I practice?
At least 100+, but honestly the more the better.
Is SQL hard to learn?
Not really — but the logic part takes practice.
What is the hardest SQL topic?
JOINs and window functions, for most people.
Difference between WHERE and HAVING?
WHERE filters rows before grouping. HAVING filters grouped results after GROUP BY.
What is a JOIN?
Combining rows from multiple tables based on a related column.
How to get a data job using SQL?
Practice consistently, build projects, and work with real datasets.
Is SQL alone enough for data jobs?
Usually not — tools like Python and Power BI are expected too.
Best way to learn SQL fast?
Daily practice. Solve real problems, not just tutorials.
Do companies actually ask SQL in interviews?
Yes — almost every tech and data role tests SQL.
Best platforms to practice?
LeetCode, HackerRank, Mode Analytics, StrataScratch.
Final Thoughts
SQL is not about syntax… it is about thinking clearly. At the start you struggle — then suddenly everything starts making sense. Just keep practicing. Even if the query is wrong, that is okay. That is how real learning happens.
Practice100+
Joins🔗
Window📊
