SQL Interview Questions for Data Analyst (2026 Guide)
Master 2026 SQL interview questions for data analysts. Real queries, window functions, joins, common traps, and runnable code solutions.
In technical hiring rounds for data analysts, SQL screening filters out over 65% of applicants before they ever speak with a hiring manager. Engineering leads and analytics directors look beyond basic SELECT * FROM table queries. They want to verify that you understand data grain, edge cases, metric reconciliation, and execution efficiency on real production schemas.
Whether you are preparing for product tech companies, financial institutions, or high-growth startups, this practical guide details the core problem archetypes tested in live coding screens. You can review current salary benchmarks in our Data Analyst Salary Guide 2026 or follow the complete curriculum in our 12-Week Data Analyst Career Track.
The 5 Core SQL Archetypes Tested in Analyst Interviews
Technical interviewers structure SQL assessments around 5 recurring patterns that reflect daily data pipeline responsibilities:
- Relational Grain & Multi-Table JOINs: Combining transactional ledgers with dimensional lookups while preventing accidental record duplication.
- Window Calculations: Evaluating ranking (
ROW_NUMBER,DENSE_RANK), offsets (LAG,LEAD), and cumulative aggregates (SUM() OVER) without collapsing row detail. - Grouped Aggregations & Conditional Logic: Filtering aggregated buckets using
HAVINGalongside conditional metrics withCASE WHEN. - Cohort Analysis & Churn Modeling: Calculating month-over-month growth, rolling 7-day averages, and multi-touch user retention via Common Table Expressions (
WITH). - Three-Valued Boolean Logic & NULL Traps: Guarding against unexpected row drops during
NOT INsubqueries and join comparisons.
Let us dissect each archetype with concrete tables, executable queries, and common pitfalls.
Scenario 1: Multi-Table JOINs & The Metric Fan-Out Trap
Interview Prompt
You have two tables: orders containing 1,200 unique customer transactions and order_items containing 3,400 line-item product details. Write a query to compute the total revenue and the total discount amount applied per customer.
The Common Pitfall: Metric Inflation
Many candidates immediately join both tables on customer_id and compute SUM(o.discount_amount) alongside SUM(oi.item_price * oi.quantity). Because one order contains an average of 2.8 line items, joining on customer_id causes order-level records to duplicate. The discount total inflates by 280%, corrupting financial ledgers. For deeper coverage on this phenomenon, explore our detailed SQL JOIN Fan-Out Guide.
Schema Context
Table: orders
| order_id | customer_id | order_date | discount_amount |
|---|---|---|---|
| 101 | 501 | 2026-01-10 | ₹50.00 |
| 102 | 501 | 2026-01-15 | ₹20.00 |
| 103 | 502 | 2026-01-12 | ₹0.00 |
Table: order_items
| item_id | order_id | product_id | quantity | unit_price |
|---|---|---|---|---|
| 1 | 101 | 801 | 2 | ₹200.00 |
| 2 | 101 | 802 | 1 | ₹150.00 |
| 3 | 102 | 803 | 1 | ₹400.00 |
| 4 | 103 | 804 | 3 | ₹100.00 |
Production Solution: Pre-Aggregation in a CTE
To avoid fan-out multiplication, aggregate the line items to the order grain before joining to the orders table:
WITH order_item_totals AS (
SELECT order_id,
SUM(quantity * unit_price) AS gross_item_revenue
FROM order_items
GROUP BY order_id
),
customer_order_summary AS (
SELECT o.customer_id,
SUM(o.discount_amount) AS total_discounts,
SUM(oit.gross_item_revenue) AS total_gross_revenue
FROM orders o
LEFT JOIN order_item_totals oit
ON o.order_id = oit.order_id
GROUP BY o.customer_id
)
SELECT customer_id,
ROUND(total_gross_revenue, 2) AS gross_revenue,
ROUND(total_discounts, 2) AS total_discounts,
ROUND(total_gross_revenue - total_discounts, 2) AS net_revenue
FROM customer_order_summary
ORDER BY customer_id;Expected Output
| customer_id | gross_revenue | total_discounts | net_revenue |
|---|---|---|---|
| 501 | ₹950.00 | ₹70.00 | ₹880.00 |
| 502 | ₹300.00 | ₹0.00 | ₹300.00 |
Interview Explanatory Script
State to the interviewer: "I pre-aggregate line items to the order grain inside a CTE first. If I joined order items directly, each order discount would be summed multiple times across its child items, creating a metric fan-out bug."
Scenario 2: Ranking & Deduplication with Window Functions
Interview Prompt
Given an employees table with employee IDs, department names, and annual salaries, retrieve the employees who earn the second-highest salary in each department. If two employees share the second-highest salary, include both.
Schema Context
Table: employees
| employee_id | employee_name | department | salary |
|---|---|---|---|
| 1 | Aarav | Analytics | ₹22,00,000 |
| 2 | Bhavna | Analytics | ₹22,00,000 |
| 3 | Chirag | Analytics | ₹18,00,000 |
| 4 | Divya | Analytics | ₹15,00,000 |
| 5 | Eshan | Engineering | ₹32,00,000 |
| 6 | Fatima | Engineering | ₹28,00,000 |
| 7 | Gopal | Engineering | ₹28,00,000 |
Choosing the Correct Ranking Function
In ranking questions, candidate failure usually stems from confusing ROW_NUMBER(), RANK(), and DENSE_RANK():
ROW_NUMBER()assigns sequential numbers (1, 2, 3, 4), which would arbitrarily pick between Aarav and Bhavna at rank 1 and push Chirag to rank 3.RANK()assigns tied rows the same number but skips subsequent ranks (1, 1, 3). In Analytics, Chirag would receive rank 3, meaning aWHERE rank = 2filter would return zero rows.DENSE_RANK()assigns tied rows the same number without skipping (1, 1, 2). Chirag receives rank 2, correctly reflecting the second distinct salary tier. Learn more about these nuances in our guide on ROW_NUMBER vs RANK vs DENSE_RANK in SQL.
Production Solution
WITH ranked_salaries AS (
SELECT employee_id,
employee_name,
department,
salary,
DENSE_RANK() OVER (
PARTITION BY department
ORDER BY salary DESC
) AS salary_rank
FROM employees
)
SELECT employee_id,
employee_name,
department,
salary
FROM ranked_salaries
WHERE salary_rank = 2
ORDER BY department, employee_name;Expected Output
| employee_id | employee_name | department | salary |
|---|---|---|---|
| 3 | Chirag | Analytics | ₹18,00,000 |
| 6 | Fatima | Engineering | ₹28,00,000 |
| 7 | Gopal | Engineering | ₹28,00,000 |
Notice that Engineering returned both Fatima and Gopal because both earn ₹28,00,000, which represents the second-highest salary tier.
Practice Real SQL Interview Questions
Master joins, window functions, and aggregations with 190+ interactive interview challenges in our live browser sandbox.
Start Practicing FreeScenario 3: Time-Series Deltas & Month-over-Month Growth
Interview Prompt
You have a table of monthly sales figures. Write a query to compute the month-over-month (MoM) revenue growth percentage for each month in 2025.
Schema Context
Table: monthly_revenue
| sale_month | total_revenue |
|---|---|
| 2025-01-01 | ₹1,20,000 |
| 2025-02-01 | ₹1,38,000 |
| 2025-03-01 | ₹1,55,250 |
| 2025-04-01 | ₹1,42,830 |
Production Solution Using LAG()
The LAG() window function allows you to inspect previous rows within an ordered partition without performing an expensive self-join:
WITH revenue_with_lag AS (
SELECT sale_month,
total_revenue,
LAG(total_revenue, 1) OVER (ORDER BY sale_month ASC) AS prev_month_revenue
FROM monthly_revenue
)
SELECT sale_month,
total_revenue,
prev_month_revenue,
ROUND(
((total_revenue - prev_month_revenue) / NULLIF(prev_month_revenue, 0)) * 100.0,
2
) AS mom_growth_pct
FROM revenue_with_lag
ORDER BY sale_month ASC;Expected Output
| sale_month | total_revenue | prev_month_revenue | mom_growth_pct |
|---|---|---|---|
| 2025-01-01 | ₹1,20,000 | NULL | NULL |
| 2025-02-01 | ₹1,38,000 | ₹1,20,000 | 15.00% |
| 2025-03-01 | ₹1,55,250 | ₹1,38,000 | 12.50% |
| 2025-04-01 | ₹1,42,830 | ₹1,55,250 | -8.00% |
Defensive SQL: NULLIF for Division by Zero
Always wrap divisor expressions in NULLIF(denominator, 0). If a prior month recorded ₹0 in revenue, raw division causes a runtime database exception (division by zero), which marks an automatic deduction in screening rounds.
Scenario 4: User Retention & 30-Day Cohort Activity
Interview Prompt
You have an activity_log table tracking user login events. Write a query to determine the percentage of users who returned to the application between 1 and 30 days after their initial signup date.
Schema Context
Table: activity_log
| user_id | event_type | event_timestamp |
|---|---|---|
| 1001 | signup | 2026-01-01 10:14:00 |
| 1001 | login | 2026-01-05 14:22:00 |
| 1002 | signup | 2026-01-02 09:30:00 |
| 1003 | signup | 2026-01-03 18:45:00 |
| 1003 | login | 2026-02-15 11:10:00 |
Production Solution: Cohort Modeling with CTEs
WITH user_signups AS (
SELECT user_id,
MIN(event_timestamp)::date AS signup_date
FROM activity_log
WHERE event_type = signup
GROUP BY user_id
),
subsequent_logins AS (
SELECT DISTINCT
a.user_id,
a.event_timestamp::date AS login_date
FROM activity_log a
WHERE a.event_type = login
),
retained_users AS (
SELECT s.user_id,
s.signup_date,
CASE
WHEN COUNT(l.login_date) > 0 THEN 1
ELSE 0
END AS is_retained_30d
FROM user_signups s
LEFT JOIN subsequent_logins l
ON s.user_id = l.user_id
AND l.login_date > s.signup_date
AND l.login_date <= s.signup_date + INTERVAL '30 days'
GROUP BY s.user_id, s.signup_date
)
SELECT COUNT(*) AS total_signups,
SUM(is_retained_30d) AS retained_users_30d,
ROUND((SUM(is_retained_30d)::numeric / COUNT(*)) * 100.0, 2) AS retention_rate_30d_pct
FROM retained_users;Key Technical Details
- Inequality JOIN in ON Clause: The condition
l.login_date > s.signup_date AND l.login_date <= s.signup_date + INTERVAL '30 days'belongs inside theONclause. Moving this filter toWHEREwould eliminate users with zero logins, making true retention calculations impossible. - Double Counting Prevention: The
DISTINCTkeyword insidesubsequent_loginsensures that multiple logins on the same day do not inflate cohort volumes.
Scenario 5: Three-Valued Logic & The NOT IN (NULL) Trap
Interview Prompt
Identify all department IDs in the departments table that currently have no employees assigned to them in the employees table.
Schema Context
Table: departments
| department_id | department_name |
|---|---|
| 10 | Growth |
| 20 | Operations |
| 30 | Legal |
Table: employees
| employee_id | employee_name | department_id |
|---|---|---|
| 1 | Ananya | 10 |
| 2 | Balram | 20 |
| 3 | Contractor | NULL |
The Trap Query: NOT IN
Many candidates write:
-- DANGEROUS: Returns 0 rows if employees contains even a single NULL
SELECT department_id, department_name
FROM departments
WHERE department_id NOT IN (
SELECT department_id FROM employees
);Why NOT IN Fails with NULL Values
In ANSI SQL, comparison with NULL yields UNKNOWN. The expression department_id NOT IN (10, 20, NULL) expands to:
department_id != 10 AND department_id != 20 AND department_id != NULL
Since department_id != NULL evaluates to UNKNOWN, the entire compound AND condition evaluates to UNKNOWN or FALSE for every single record. As a result, the query returns 0 rows.
The Robust Production Fix: NOT EXISTS
SELECT d.department_id,
d.department_name
FROM departments d
WHERE NOT EXISTS (
SELECT 1
FROM employees e
WHERE e.department_id = d.department_id
);NOT EXISTS relies on two-valued existential checks (whether a row exists matching the predicate or not), rendering it completely immune to NULL values in subqueries.
The 4-Step Communication Framework for Live Technical Rounds
Senior interviewers score candidates not just on final syntax, but on problem decomposition and defensive reasoning:
| Step | Interview Action | What to Say Aloud |
|---|---|---|
| 1. Clarify Grain & Keys | Identify primary keys and dimensional grain. | "Before writing code, let me verify: is order_id unique in orders, or can orders span multiple rows?" |
| 2. Audit Edge Cases | Probe for NULLs, zero values, and duplicates. | "How should we treat records with NULL department IDs or transactions with negative refund totals?" |
| 3. Structure with CTEs | Break complex pipelines into readable layers. | "I will organize this into two CTEs: first computing order totals, then aggregating customer lifetime figures." |
| 4. Dry-Run Verification | Trace an example record through your query. | "Let us walk row 101 through the join to verify that the discount is applied once rather than three times." |
Practice Checklist for Data Analyst Interviews
Before attending your next technical screening, ensure you can execute these operations from memory without consulting documentation:
- Deduplicate tables deterministically using
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) - Distinguish between
WHERE(pre-aggregation filter) andHAVING(post-aggregation filter) - Write multi-period window offsets using
LAG()andLEAD()with explicitNULLIFzero guards - Convert dates and extract intervals using standard ANSI syntax (
DATE_TRUNC,INTERVAL) - Combine multiple datasets safely using
UNION ALLinstead of slowerUNIONwhen records are known to be distinct
Accelerate Your Data Analyst Career
Build production-grade SQL and Python skills with portfolio projects and interview prep in our structured 12-week track.
Explore the Career TrackFrequently Asked Questions
What are the most common SQL interview questions for data analysts?
The five most common topics are multi-table JOINs with fan-out prevention, ranking functions (ROW_NUMBER vs DENSE_RANK), aggregate filters using HAVING vs WHERE, cohort retention with CTEs, and NULL logic using COALESCE.
How do you find the second highest salary in SQL?
The standard production approach uses DENSE_RANK() OVER (ORDER BY salary DESC) inside a CTE, filtering WHERE rank = 2. This reliably handles ties where multiple employees share the top salary.
Why does a WHERE condition turn a LEFT JOIN into an INNER JOIN?
A LEFT JOIN keeps unmatched left rows with NULL values for right table columns. Placing a right table filter in WHERE discards rows where the column IS NULL, effectively converting the query into an INNER JOIN. Place right table filters inside the ON clause to preserve left rows.
How do interviewers evaluate live SQL coding rounds?
Interviewers look for table grain clarity, duplicate key awareness, edge case testing with NULLs, modular structure using CTEs, and clear verbal communication before writing code.

Written by
Founder at Topfolio with 6+ years in data & analytics across JPMC, Ultrahuman, and high-growth startups. Sat on hiring panels, reviewed 500+ resumes, and writes practical SQL & data guides.
Related Articles
Data Analyst Interview Questions 2026: Complete Preparation Guide
30+ real data analyst interview questions with schemas, solutions & pitfalls — SQL OAs vs live technical rounds, Python, modern data stack, product cases & behavioral.
SQL Joins Practice Exercises: 15 Real Queries & Answers
Master SQL joins with 15 real business practice exercises. Solve INNER, LEFT, RIGHT, FULL OUTER, CROSS, and SELF JOINs with schemas and expected outputs.
SQL Practice Questions: 25 Real Business Queries & Answers
Practice 25 real-world SQL queries with solutions, schema diagrams, and expected outputs. Master joins, window functions, and aggregations for interviews.