🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

SQL Interview Questions 2026: JOINs, Window Functions and Optimization

⏱️5 min read  ·  1,026 words

SQL remains the most important query language in 2026 — every data role, backend developer position, and analytics job requires SQL proficiency. This guide covers the most common SQL interview questions from basic SELECT statements to advanced window functions, query optimization, and database design.

Basic SQL Questions

1. What is the difference between WHERE and HAVING?

-- WHERE filters rows BEFORE grouping
SELECT department, COUNT(*) as count
FROM employees
WHERE salary > 50000     -- filters rows first
GROUP BY department;

-- HAVING filters groups AFTER GROUP BY
SELECT department, AVG(salary) as avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 60000;  -- filters groups

-- Key rule: WHERE cannot use aggregate functions
-- HAVING can use aggregate functions

2. Explain the different types of JOINs

-- Setup: users and orders tables
-- users: id, name
-- orders: id, user_id, total

-- INNER JOIN — only matching rows
SELECT u.name, o.total
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
-- Returns only users who HAVE orders

-- LEFT JOIN — all from left, matching from right
SELECT u.name, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
-- Returns ALL users (total=NULL if no orders)

-- RIGHT JOIN — all from right, matching from left
-- Rarely used (just flip LEFT JOIN)

-- FULL OUTER JOIN — all from both
SELECT u.name, o.total
FROM users u
FULL OUTER JOIN orders o ON u.id = o.user_id;

-- CROSS JOIN — every combination (cartesian product)
SELECT u.name, p.name
FROM users u
CROSS JOIN products p;  -- n x m rows

3. What are indexes and when should you use them?

-- Index = B-tree data structure for fast lookups

-- When to create:
-- 1. Frequently queried columns (WHERE, JOIN ON, ORDER BY)
CREATE INDEX idx_users_email ON users(email);

-- 2. Compound index (for multi-column queries)
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC);

-- 3. Unique constraint
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);

-- When NOT to create:
-- - Small tables (full scan may be faster)
-- - Frequently updated columns (updates must update index too)
-- - Low-cardinality columns (e.g., gender with 2 values)

-- Check if index is used
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';

Intermediate Questions

4. Write a query to find the second highest salary

-- Method 1: LIMIT/OFFSET
SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1;

-- Method 2: Subquery
SELECT MAX(salary) as second_highest
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);

-- Method 3: Dense rank (best for Nth highest)
SELECT salary
FROM (
    SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) as rank
    FROM employees
) ranked
WHERE rank = 2
LIMIT 1;

5. Find duplicate records

-- Find duplicates
SELECT email, COUNT(*) as count
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

-- Show all duplicate rows
SELECT *
FROM users u1
WHERE EXISTS (
    SELECT 1 FROM users u2
    WHERE u2.email = u1.email
    AND u2.id != u1.id
);

-- Delete duplicates (keep lowest ID)
DELETE FROM users
WHERE id NOT IN (
    SELECT MIN(id)
    FROM users
    GROUP BY email
);

6. Explain window functions

-- Window functions operate on a set of rows related to current row
SELECT
    name,
    department,
    salary,
    AVG(salary) OVER (PARTITION BY department) as dept_avg,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dept_rank,
    salary - LAG(salary) OVER (ORDER BY hire_date) as salary_change,
    SUM(salary) OVER (ORDER BY hire_date ROWS UNBOUNDED PRECEDING) as running_total
FROM employees;

-- ROW_NUMBER vs RANK vs DENSE_RANK
-- ROW_NUMBER: 1,2,3,4,5 (no ties)
-- RANK:       1,2,2,4,5 (ties get same rank, gap after)
-- DENSE_RANK: 1,2,2,3,4 (ties get same rank, no gap)

Advanced Questions

7. What are CTEs and when to use them?

-- Common Table Expression (WITH clause)
WITH
monthly_revenue AS (
    SELECT
        DATE_TRUNC('month', created_at) AS month,
        SUM(total) AS revenue
    FROM orders
    GROUP BY 1
),
growth_rates AS (
    SELECT
        month,
        revenue,
        LAG(revenue) OVER (ORDER BY month) AS prev_month,
        ROUND(
            100.0 * (revenue - LAG(revenue) OVER (ORDER BY month)) /
            LAG(revenue) OVER (ORDER BY month), 2
        ) AS growth_pct
    FROM monthly_revenue
)
SELECT * FROM growth_rates WHERE month >= '2026-01-01';

-- Benefits: readability, reusability, no nested subquery hell
-- Recursive CTE: for hierarchical data (org charts, file trees)

8. What is EXPLAIN and how do you use it to optimize queries?

EXPLAIN ANALYZE SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > NOW() - INTERVAL '30 days'
GROUP BY u.name
HAVING COUNT(o.id) > 5
ORDER BY order_count DESC;

-- Look for:
-- Seq Scan (sequential scan) on large table → needs index
-- Index Scan → using index (good)
-- Hash Join vs Nested Loop → Hash better for large tables
-- High cost estimates → consider query rewrite
-- rows= vs actual rows= discrepancy → run ANALYZE

9. ACID properties — what are they?

  • Atomicity — all operations in transaction succeed or all fail
  • Consistency — database goes from one valid state to another
  • Isolation — concurrent transactions don’t see each other’s intermediate state
  • Durability — committed transactions survive system failures

10. What is the N+1 query problem and how do you fix it?

-- N+1: 1 query for list, then 1 query per item
-- Bad (N+1 in application):
-- SELECT * FROM users              -- 1 query
-- SELECT * FROM orders WHERE user_id = 1  -- then N more queries!

-- Fix: JOIN to get data in one query
SELECT u.id, u.name, o.id as order_id, o.total
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.id IN (1, 2, 3);

-- Or use subquery for aggregates
SELECT u.id, u.name,
    (SELECT COUNT(*) FROM orders WHERE user_id = u.id) as order_count
FROM users u;

SQL interview preparation: practice writing queries from scratch, understand JOINs deeply, know window functions cold, and be able to explain EXPLAIN output. Most SQL interviews include a practical component — practice on leetcode.com/study-plan/sql/ or sqlzoo.net. Time your queries and optimize them.

✍️ Leave a Comment

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

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা