Live Batches:Online & Gurugram
Technical Interview Guide10 Practical Questions

SQL Interview Questions for Data Analysts

SQL is the non-negotiable hiring filter for Data Analysts globally. Technical interviewers evaluate whether you can write clean, performant queries on real transactional databases. Review these core questions with query examples, underlying logic, and common pitfalls.

1Filtering & Aggregation

What is the fundamental difference between WHERE and HAVING in SQL?

What the Interviewer is Testing: Understanding the SQL query execution order (FROM → WHERE → GROUP BY → HAVING → SELECT).
Model Answer:

WHERE filters individual row records before any grouping or aggregation occurs. HAVING filters grouped rows after the GROUP BY clause has calculated summary values (such as SUM, COUNT, or AVG).

Code Implementation:
-- WHERE filters uncancelled orders; HAVING filters customers with >3 orders
SELECT customer_id, COUNT(order_id) AS total_orders, SUM(amount) AS total_spend
FROM orders
WHERE status != 'cancelled'
GROUP BY customer_id
HAVING COUNT(order_id) > 3;
Common Candidate Mistake: Attempting to place aggregate functions like SUM() or COUNT() inside the WHERE clause, which results in a syntax error.
2Relational Joins

What is the operational difference between INNER JOIN and LEFT JOIN, and how are NULLs handled?

What the Interviewer is Testing: Understanding relational data integrity and preserving records across multiple tables.
Model Answer:

An INNER JOIN returns only records that have matching keys in both tables. A LEFT JOIN returns all rows from the left table regardless of matches, filling columns from the right table with NULL when no match exists.

Code Implementation:
-- LEFT JOIN ensures customers with zero purchases are still returned
SELECT c.customer_id, c.customer_name, o.order_id, COALESCE(o.amount, 0) AS amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
Common Candidate Mistake: Applying a WHERE filter on the right table without checking for NULLs, which unintentionally converts the LEFT JOIN into an INNER JOIN.
3Window Functions

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

What the Interviewer is Testing: Knowledge of analytical ranking functions and tie-breaking behavior.
Model Answer:

ROW_NUMBER assigns unique sequential integers (1, 2, 3...) regardless of tied values. RANK assigns identical rank numbers to ties and skips subsequent numbers (e.g., 1, 2, 2, 4). DENSE_RANK assigns identical numbers to ties without skipping subsequent ranks (e.g., 1, 2, 2, 3).

Code Implementation:
SELECT employee_id, department_id, salary,
       ROW_NUMBER() OVER(PARTITION BY department_id ORDER BY salary DESC) AS row_num,
       RANK()       OVER(PARTITION BY department_id ORDER BY salary DESC) AS rnk,
       DENSE_RANK() OVER(PARTITION BY department_id ORDER BY salary DESC) AS dense_rnk
FROM employees;
Common Candidate Mistake: Using RANK() instead of DENSE_RANK() when calculating the N-th highest salary, which causes incorrect outputs when ties exist.
4Business Problems

How do you write a query to find the N-th highest salary (e.g., 2nd highest) in an organization?

What the Interviewer is Testing: Ability to combine window functions or subqueries to solve classic hiring puzzles.
Model Answer:

The modern, standard ANSI SQL solution uses a Common Table Expression (CTE) with DENSE_RANK() partitioned across the company or department, followed by filtering where dense_rank = N.

Code Implementation:
WITH ranked_salaries AS (
  SELECT employee_id, salary, department_id,
         DENSE_RANK() OVER(ORDER BY salary DESC) AS rank_pos
  FROM employees
)
SELECT employee_id, salary
FROM ranked_salaries
WHERE rank_pos = 2;
Common Candidate Mistake: Relying on LIMIT 1 OFFSET 1 without handling salary duplicates, which returns incorrect results if two top earners share the exact same salary.
5Query Architecture

What is a Common Table Expression (CTE) and why is it preferred over nested subqueries?

What the Interviewer is Testing: Code readability, modularity, and understanding query execution plan structures.
Model Answer:

A Common Table Expression (WITH clause) defines a temporary, named result set that exists only for the duration of the query. CTEs break complex, nested multi-level subqueries into readable, step-by-step modular blocks that are significantly easier to debug and maintain.

Code Implementation:
WITH monthly_sales AS (
  SELECT DATE_TRUNC('month', order_date) AS sales_month, SUM(amount) AS revenue
  FROM orders
  GROUP BY 1
),
growth_calc AS (
  SELECT sales_month, revenue,
         LAG(revenue) OVER(ORDER BY sales_month) AS prior_month_rev
  FROM monthly_sales
)
SELECT sales_month, revenue,
       ROUND(((revenue - prior_month_rev) / prior_month_rev) * 100, 2) AS mom_growth_pct
FROM growth_calc;
Common Candidate Mistake: Writing deeply nested subqueries 4 levels deep that are impossible to test independently or review during team pull requests.
6Window Functions

How do you compute a running total (cumulative sum) in SQL?

What the Interviewer is Testing: Understanding window frame specifications (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW).
Model Answer:

A running total is computed by applying the SUM() aggregate function with an OVER() clause ordered chronologically by date or order ID without a GROUP BY collapse.

Code Implementation:
SELECT order_date, amount,
       SUM(amount) OVER(ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_revenue
FROM orders;
Common Candidate Mistake: Forgetting the ORDER BY inside the OVER() clause, which results in calculating the grand total for all rows rather than a cumulative running sum.
7Data Hygiene

How do you detect and delete duplicate records from a table in SQL?

What the Interviewer is Testing: Practical data cleaning and using ROW_NUMBER() for record deduplication.
Model Answer:

Duplicate records are identified by partitioning by the unique identifying columns and assigning a ROW_NUMBER(). Any row where row_num > 1 represents a duplicate.

Code Implementation:
WITH numbered_duplicates AS (
  SELECT *,
         ROW_NUMBER() OVER(PARTITION BY email, first_name, last_name ORDER BY created_at) AS row_num
  FROM customers
)
DELETE FROM customers
WHERE customer_id IN (
  SELECT customer_id FROM numbered_duplicates WHERE row_num > 1
);
Common Candidate Mistake: Partitioning by an auto-incrementing primary key (like id) instead of business entity fields (like email or tax ID), which fails to detect true commercial duplicates.
8Aggregations

How do you perform conditional aggregation in a single query using CASE WHEN?

What the Interviewer is Testing: Pivoting tabular data and computing multiple segment metrics in one pass.
Model Answer:

Conditional aggregation embeds a CASE WHEN statement inside SUM() or COUNT() functions. This allows computing active vs churned users or regional sales side-by-side without writing multiple subqueries.

Code Implementation:
SELECT DATE_TRUNC('month', order_date) AS order_month,
       COUNT(order_id) AS total_orders,
       SUM(CASE WHEN status = 'delivered' THEN amount ELSE 0 END) AS delivered_revenue,
       SUM(CASE WHEN status = 'returned'  THEN amount ELSE 0 END) AS returned_amount
FROM orders
GROUP BY 1
ORDER BY 1;
Common Candidate Mistake: Using COUNT(CASE WHEN condition THEN 0 END) — COUNT counts zeros because zero is non-null! Use SUM(...) or COUNT(CASE WHEN condition THEN 1 END).
9Window Functions

How do LAG() and LEAD() functions work in period-over-period comparisons?

What the Interviewer is Testing: Comparing time-series values across consecutive rows without self-joins.
Model Answer:

LAG() accesses data from a previous row at a specified physical offset, while LEAD() accesses data from a subsequent row. They are the standard tools for calculating Month-over-Month (MoM) and Year-over-Year (YoY) variances.

Code Implementation:
SELECT order_date, daily_revenue,
       LAG(daily_revenue, 1) OVER(ORDER BY order_date) AS prior_day_revenue,
       daily_revenue - LAG(daily_revenue, 1) OVER(ORDER BY order_date) AS daily_change
FROM daily_sales_summary;
Common Candidate Mistake: Assuming chronological order without explicitly writing ORDER BY inside the OVER() clause.
10Business Problems

How do you calculate customer cohort retention in SQL?

What the Interviewer is Testing: Advanced data modeling, multiple CTEs, date truncation, and cohort retention math.
Model Answer:

First, identify each user's first purchase month (Cohort Month) using MIN(order_date). Second, join subsequent orders to calculate Month-0, Month-1, and Month-3 repeat purchase activity.

Code Implementation:
WITH first_purchase AS (
  SELECT customer_id, DATE_TRUNC('month', MIN(order_date)) AS cohort_month
  FROM orders
  GROUP BY customer_id
),
subsequent_orders AS (
  SELECT o.customer_id, fp.cohort_month,
         DATE_TRUNC('month', o.order_date) AS order_month
  FROM orders o
  JOIN first_purchase fp ON o.customer_id = fp.customer_id
)
SELECT cohort_month, order_month,
       COUNT(DISTINCT customer_id) AS retained_customers
FROM subsequent_orders
GROUP BY 1, 2
ORDER BY 1, 2;
Common Candidate Mistake: Counting total order rows instead of distinct customers (`COUNT(DISTINCT customer_id)`), distorting retention metrics when repeat users place multiple orders in a month.
Live Technical Interview Preparation

Practice Live Technical Assessments with SSSAM Faculty

Reading questions is the first step. Writing queries under timed screen-share tests with live mentor reviews builds genuine interview confidence.

Book Free Demo