Skip to content

100+ SQL Practice Questions with Solutions

    SQL Practice Questions with Solutions

    Last Updated on: 30th March 2026, 01:12 pm

    Advertisement
    100+ SQL Practice Questions · brutalist query lab

    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.
    TableColumns
    Studentsid, name, marks, city, dept_id
    Departmentsid, dept_name
    Employeesid, 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 = NULL instead of IS 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📊

    Share this post on social!

    Comment on Post

    Your email address will not be published. Required fields are marked *