Cohort 4Register
SQL Interview Questions

SQL Interview Questions and Answers for Data Analyst and Engineering Roles

Practice SQL interview questions across joins, aggregations, window functions, CTEs, ranking, date logic, case statements, optimization basics, and business cases.

By Durgesh Yadav — Senior Data Engineer @ 7-Eleven · Updated 2 Oct 2026. Preparation guidance, not a hiring guarantee.

Jump to 23 answered questionsBeginner to Advanced · 10 FAQs · 10-part guide

What SQL topics come up most in data interviews?

Most Indian data-analyst and data-engineer interviews test five SQL areas: joins across multiple tables, aggregations with GROUP BY and HAVING, window functions (ROW_NUMBER, RANK, LAG), CTEs for readable multi-step logic, and date/case business scenarios. Freshers should master joins and aggregations first — they appear in nearly every screening round.

Guide

What To Learn And How To Practice

What is SQL, and why is it used?

SQL (Structured Query Language) is the language for asking questions of data stored in tables. You say which rows and columns you want, how to join tables and how to summarise them, and the database works out how to fetch the answer. Analysts use it to pull and aggregate data; engineers use it to build and check pipelines.

Example: SELECT city, SUM(amount) FROM orders GROUP BY city returns total sales per city in one statement
Why it matters for analysts: SQL was the most-named of the 18 technologies counted in the 2,467 data-analyst postings of PrepNPlaced's India Tech Hiring Report (34.6%, 23 August to 27 September 2026), and Jobright's 2025-26 posting data puts it in more than 80% of data analyst postings
Why it matters for engineers: 48.6% of the report's 1,470 data-engineer postings named SQL
The parts interviewers name: DDL (CREATE, ALTER, DROP), DML (INSERT, UPDATE, DELETE), DQL (SELECT), DCL (GRANT, REVOKE) and TCL (COMMIT, ROLLBACK)

What do beginner SQL questions cover?

Beginner rounds usually check whether you understand filtering, joins, grouping, null handling, and reading table relationships.

Explain inner join vs left join with an example
Find customers with no orders
Count orders by month and status

Answer hint

For no-order questions, start from customers and left join orders, then filter where the order key is null.

Common mistake

Filtering the right table in WHERE after a left join can accidentally turn it into an inner join.

What is the difference between DELETE, TRUNCATE and DROP?

DELETE is the only one that takes a WHERE, so it is the answer whenever some rows must stay. TRUNCATE empties a table you intend to reload, which is the usual staging-table case. DROP removes the table itself, and the follow-up we ask is what happens to rollback, triggers and identity counters under each.

Choose by what must survive the statement
DimensionDELETETRUNCATEDROP
What goes awayRows matching the WHERE, or all rowsEvery row; the table staysThe table, its data and its definition
WHERE clauseYesNoNo
Rollback inside a transactionYesPostgres and SQL Server yes; MySQL and Oracle auto-commit itSame split as TRUNCATE
Row DELETE triggers fireYes, per rowNoNo
Auto-increment counterKeeps countingReset in MySQL and SQL Server; Postgres needs RESTART IDENTITYGone with the table
Fits whenSome rows must stay, or triggers must runReloading a staging table from scratchThe table should not exist any more

How do intermediate SQL questions raise the bar?

Intermediate questions test CTEs, subqueries, case statements, date functions, and converting business wording into query logic.

Calculate repeat purchase rate
Segment users by activity bands
Find the first transaction per customer

What shows up in advanced SQL rounds?

Advanced rounds usually involve window functions, ranking, cohort logic, rolling metrics, deduplication, and query performance tradeoffs.

Rank top products per category
Calculate 7-day rolling revenue
Deduplicate records using row_number

Window function hint

Use partition by for the group, order by for sequence, and choose row_number, rank, or dense_rank based on tie behavior.

Optimization hint

Explain filters, join keys, selected columns, indexes/partitioning, and why a query scans too much data.

Which SQL joins questions come up, and what does each join return?

Joins questions are asked on a tiny dataset so the interviewer can watch you count rows. Use this one: customers has three rows (1 Asha, 2 Bilal, 3 Chitra) and orders has three rows (101 for customer 1 at 500, 102 for customer 1 at 300, 103 for customer 2 at 250). Chitra has never ordered. Say the row count before you write the query; that is the part being tested. INNER JOIN returns 3 rows, one per order, and Chitra disappears. LEFT JOIN from customers returns 4 rows: Asha twice, Bilal once, Chitra once with NULL in every orders column. RIGHT JOIN from customers to orders returns the same 3 rows as the inner join here, because every order has a customer. FULL OUTER JOIN returns 4 rows. CROSS JOIN returns 9, every customer against every order, and it is the answer to "why did my report multiply" more often than anyone admits. The follow-up is always fan-out: if orders had two rows for one order id, an inner join on customer_id doubles Asha's rows and any SUM you run afterwards is wrong. Check the join key's uniqueness on both sides before you aggregate.

State the row count each join returns on the three-customer dataset before you write it
Customers with no orders: LEFT JOIN plus WHERE o.id IS NULL, or NOT EXISTS
Why a LEFT JOIN returned more rows than the left table: duplicate keys on the right side
Self join: employees to their managers, with a LEFT JOIN so the CEO still appears

Total per customer, keeping customers with no orders

SELECT c.name, COALESCE(SUM(o.amount), 0) AS total FROM customers c LEFT JOIN orders o ON o.customer_id = c.id GROUP BY c.name. Output: Asha 800, Bilal 250, Chitra 0. Without COALESCE, Chitra shows NULL; with an INNER JOIN she is missing entirely.

The condition that belongs in ON, not WHERE

To list every customer and only their orders above 400: LEFT JOIN orders o ON o.customer_id = c.id AND o.amount > 400. Putting amount > 400 in WHERE drops Bilal and Chitra, because their NULL amount fails the test.

Employees and their managers

SELECT e.name, m.name AS manager FROM employees e LEFT JOIN employees m ON m.id = e.manager_id. An INNER JOIN here silently removes anyone without a manager, which is usually the one row the interviewer checks for.

Counting the fan-out before it bites

SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id HAVING COUNT(*) > 1 tells you which side of a join is not unique. Interviewers rate a candidate who runs this check higher than one who writes a longer query.

What do SQL query questions look like, and what output should you expect?

Query questions give you a small table and a business sentence, and the interviewer already knows the answer rows. Work on this table: sales has month, product and revenue. January: laptops 1,000 and phones 600. February: laptops 1,200 and phones 500. March: laptops 900 and phones 700. Say the expected output in words first, then write the query; a candidate who can predict the result rows is trusted with the harder ones.

Month-over-month growth per product with LAG
Top product per month with DENSE_RANK
Products above the overall average revenue
A 3-month rolling average, and the difference between ROWS and RANGE

Month-over-month growth

SELECT product, month, revenue, revenue - LAG(revenue) OVER (PARTITION BY product ORDER BY month) AS change FROM sales. Output for laptops: Jan NULL, Feb +200, Mar -300. The NULL in January is correct, and saying so out loud is part of the answer.

Top product per month

SELECT month, product FROM (SELECT month, product, DENSE_RANK() OVER (PARTITION BY month ORDER BY revenue DESC) AS rnk FROM sales) t WHERE rnk = 1. Output: laptops in all three months. Use DENSE_RANK rather than ROW_NUMBER so a tie returns both products instead of one picked at random.

Products above the overall average

SELECT product, SUM(revenue) AS total FROM sales GROUP BY product HAVING SUM(revenue) > (SELECT AVG(revenue) * 3 FROM sales). Laptops total 3,100 and phones 1,800; the average monthly revenue is 816.67, so the bar is 2,450 and only laptops pass. State how you scaled the average, because that is where the marks are.

3-month rolling average

AVG(revenue) OVER (PARTITION BY product ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW). For laptops: Jan 1,000, Feb 1,100, Mar 1,033.33. ROWS counts physical rows; RANGE groups equal ORDER BY values together, which changes the result when two rows share a month.

Latest order per customer

SELECT * FROM (SELECT o.*, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC, id DESC) AS rn FROM orders o) t WHERE rn = 1. The second ORDER BY column is the tie-break for two orders on the same day; leaving it out makes the result non-deterministic.

What do SQL interview questions for experienced candidates (3 to 5 years) cover?

Past three years the questions stop being about syntax and start being about judgement. The interviewer describes a real situation, usually one their own team had last quarter, and listens for what you would check first. Expect a slow query that was fast last week, a report that disagrees with finance, a load that was run twice, and a table that needs history. The pattern to the good answers: name the cause you would rule out first, say which command or query proves it, and mention the trade-off of the fix. "Add an index" without saying which columns and what it costs on writes reads as junior. So does "use a CTE" as an optimisation, since in most engines a CTE is only readability.

Read an EXPLAIN plan: scan type, join order, estimated versus actual rows, where the sort or hash spilled
Write a MERGE or upsert that is safe to run twice
Design a table that keeps history: daily snapshot versus slowly changing dimension type 2
Explain isolation levels with the one anomaly each level allows
Partition pruning: why a filter on a derived column skips no partitions

The query got slow overnight

Rule out the data first: row counts and skew on the join keys, a partition that stopped pruning because a filter now wraps the column in a function, stale statistics after a big load. Then the plan: compare estimated and actual rows, look for a nested loop that became a hash join. Only then talk about indexes, and name the columns in the order the WHERE and JOIN use them.

An upsert that survives a rerun

MERGE INTO target t USING staging s ON t.id = s.id WHEN MATCHED AND s.updated_at > t.updated_at THEN UPDATE SET ... WHEN NOT MATCHED THEN INSERT .... The updated_at guard is what makes a second run a no-op; without it, a stale staging file overwrites newer rows. Deduplicate staging on id first, or MERGE errors on multiple matches.

Snapshot or SCD2?

A daily snapshot table stores every row every day: simple, and a 1M-row dimension costs 365M rows a year. SCD type 2 stores a row only when something changes, with valid_from, valid_to and an is_current flag: cheap, and every query needs the as-of join. Pick snapshot when the table is small or the question is always 'what did it look like on date X'; pick SCD2 for large dimensions.

Read committed versus repeatable read

Under read committed, the same SELECT run twice inside one transaction can return different rows because another transaction committed in between. Repeatable read fixes that for rows already read but can still show new rows (phantoms) in some engines. Serializable prevents all of it and pays with locks or aborted transactions.

Why the partition filter did nothing

WHERE DATE(event_ts) = '2026-09-01' scans every partition because the engine cannot map a function of the column back to partition ranges. Write WHERE event_ts >= '2026-09-01' AND event_ts < '2026-09-02' instead, and the plan shows the pruned partition count.

What do interviewers look for in business case SQL rounds?

For data roles, interviewers often care less about syntax tricks and more about whether your query matches the metric definition.

Define active user before writing SQL
Clarify date range and timezone
State assumptions about refunds, cancellations, and duplicates

Which answered SQL questions do interviewers reuse most?

These are the answered questions interviewers reuse most for analyst and data roles. Practice saying the approach first, then writing the query.

Window functions and ranking
Joins and NULL handling
Aggregation and dedup logic

Find the second highest salary

Rank distinct salaries and pick rank 2: SELECT salary FROM (SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk FROM employees GROUP BY salary) t WHERE rnk = 2. DENSE_RANK shares a rank across ties so equal salaries are handled.

RANK vs DENSE_RANK vs ROW_NUMBER

All number rows within a partition. ROW_NUMBER is always unique, RANK leaves gaps after ties (1,1,3), and DENSE_RANK does not (1,1,2). Choose by whether ties should share a number and whether gaps matter.

WHERE vs HAVING

WHERE filters rows before grouping; HAVING filters groups after aggregation. You cannot reference an aggregate like COUNT(*) in WHERE, but you can in HAVING.

Remove duplicate rows but keep one

Number duplicates with ROW_NUMBER() OVER (PARTITION BY the_key_columns ORDER BY id) and keep rows where the number equals 1, deleting the rest.

The LEFT JOIN NULL-filter trap

After a LEFT JOIN, putting a condition on the right table in WHERE silently turns it into an INNER JOIN. Move that condition into the ON clause to keep unmatched rows.

7-day rolling revenue

Use SUM(amount) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW). Switch to UNBOUNDED PRECEDING for a cumulative running total.

Customers with no orders

LEFT JOIN customers to orders and filter WHERE orders.id IS NULL, or use NOT EXISTS. Be careful with NOT IN when the subquery can return NULLs.

COUNT(*) vs COUNT(col) vs COUNT(DISTINCT col)

COUNT(*) counts all rows including NULLs, COUNT(col) skips NULLs, and COUNT(DISTINCT col) counts unique non-null values. GROUP BY also collapses all NULLs into one group.

Question bank

23 interview questions with answers

Real questions from beginner to advanced, each with a concise model answer — practice them, then rehearse live in a mock interview. Every answer is open; collapse any you have already covered.

BeginnerWhat is the difference between WHERE and HAVING?

WHERE filters individual rows before any grouping happens; HAVING filters groups after GROUP BY, which is why it can reference aggregates like COUNT(*) or SUM(amount) and WHERE cannot. Logical order: FROM, WHERE, GROUP BY, HAVING, SELECT. Practical consequence: push conditions into WHERE whenever they apply to single rows — filtering early shrinks the data before the expensive grouping step. HAVING COUNT(*) > 5 is legitimate; HAVING country = 'IN' runs in most databases but wastes work grouping rows you were always going to discard. Follow-ups interviewers probe: column aliases defined in SELECT are visible in neither WHERE nor HAVING (they have not been computed yet), and most engines allow HAVING without GROUP BY, treating the entire result as one group.

BeginnerWhat are the different types of JOINs in SQL?

INNER JOIN returns only rows with a match on the join condition. LEFT JOIN keeps every row from the left table, filling the right side with NULLs where nothing matches; RIGHT JOIN is the mirror image; FULL OUTER JOIN keeps unmatched rows from both sides. CROSS JOIN produces the Cartesian product (every combination), and a SELF JOIN is a table joined to itself under two aliases — the classic employee-to-manager hierarchy. The trap interviewers set: putting a condition on the right table in the WHERE clause of a LEFT JOIN (WHERE r.status = 'active') silently converts it into an INNER JOIN, because the NULL rows fail the test — put that condition in the ON clause instead. Also worth saying: join order in the query text does not dictate execution order; the optimizer chooses.

BeginnerWhat is the difference between UNION and UNION ALL?

UNION combines two result sets and removes duplicate rows from the combined output; UNION ALL simply concatenates and keeps everything. The dedupe forces a sort or hash over the whole result, so UNION is strictly more expensive — on large sets, dramatically so. Default to UNION ALL and reach for UNION only when duplicates are genuinely possible and unwanted. Both require the same number of columns with compatible types, and column names come from the first SELECT. Nuances worth volunteering: UNION deduplicates across the inputs as well as within each one, so it can return fewer rows than either side alone; ORDER BY applies to the final combined result, not to individual branches; and if the two sides can never overlap — say, disjoint date ranges — UNION buys nothing but cost.

BeginnerWhat is the difference between DELETE, TRUNCATE, and DROP?

DELETE is DML: it removes rows one at a time (optionally filtered with WHERE), logs each removal, fires row-level triggers, and can be rolled back. TRUNCATE is DDL: it deallocates the table's data pages in one operation — much faster, minimally logged, no WHERE clause, typically resets identity/auto-increment counters, and refuses to run if foreign keys reference the table. DROP removes the table itself: data, structure, indexes, and privileges. The nuance interviewers probe: TRUNCATE is transactional and rollback-able in some engines (PostgreSQL, SQL Server) but triggers an implicit commit in others (MySQL, Oracle) — say 'it depends on the engine' rather than the folklore 'TRUNCATE cannot be rolled back'. Also: DELETE leaves the table's space fragmented; TRUNCATE returns it.

BeginnerWhat is a primary key versus a foreign key?

A primary key uniquely identifies each row: unique, NOT NULL, and one per table, though it may be composite (multi-column). A foreign key is a column (or columns) referencing a primary or unique key in another table, enforcing referential integrity: you cannot insert an order whose customer_id has no matching customer. ON DELETE / ON UPDATE clauses define what happens to children when the parent changes — CASCADE, SET NULL, or RESTRICT. Details that earn credit: a foreign key column may be NULL (an optional relationship) and may repeat; declaring a PK creates a unique index automatically, but most databases do NOT index foreign key columns for you, which makes joins and cascading deletes slow until you add one. Contrast surrogate keys (auto-increment ids) with natural keys.

BeginnerHow does NULL behave in SQL, and how do you handle it?

NULL means unknown, and SQL uses three-valued logic: any comparison with NULL yields UNKNOWN, never TRUE — so both col = NULL and col <> NULL filter out every row. Test with IS NULL / IS NOT NULL, substitute defaults with COALESCE(col, fallback), and turn sentinels into NULL with NULLIF(a, b). The traps interviewers check: NOT IN against a subquery that returns even one NULL yields zero rows (use NOT EXISTS instead); COUNT(*) counts all rows but COUNT(col) skips NULLs, and AVG ignores them too, which silently changes denominators; NULLs never equal each other in join conditions, yet DISTINCT and GROUP BY treat them as a single group; and whether NULLs sort first or last under ORDER BY is engine-specific — use NULLS FIRST/LAST where supported rather than relying on the default.

BeginnerWhat is the logical execution order of a SELECT query?

FROM (with JOINs) runs first, then WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY, and finally LIMIT/OFFSET. Each stage only sees what earlier stages produced, and that single fact explains most 'why doesn't this work' errors: a SELECT alias cannot be used in WHERE or HAVING because SELECT has not run yet (most engines do allow aliases in ORDER BY, which runs after); WHERE cannot reference aggregates because grouping has not happened; and window functions are evaluated with SELECT, so you cannot filter on ROW_NUMBER() directly — wrap the query in a CTE or subquery first. The caveat that shows depth: this is the logical order, not the physical one. The optimizer freely reorders actual execution — pushing predicates down, reordering joins — as long as the result is identical.

IntermediateWhat is the difference between RANK, DENSE_RANK, and ROW_NUMBER?

All three number rows within an OVER (ORDER BY ...) window; they differ only in tie handling. ROW_NUMBER assigns unique sequential numbers, breaking ties arbitrarily — nondeterministically unless the ORDER BY is unique. RANK gives tied rows the same rank, then skips: 1, 1, 3. DENSE_RANK gives ties the same rank with no gaps: 1, 1, 2. Choosing correctly is the real interview question: 'top 3 salaries including ties' wants DENSE_RANK; 'exactly one row per group' (deduplication) wants ROW_NUMBER with PARTITION BY; leaderboard standings where a tie consumes places want RANK. And because window functions cannot appear in WHERE, filter through a subquery:

sql
SELECT *
FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
  FROM emp
) t
WHERE rn = 1;

IntermediateHow do you find the second highest salary in a table?

Cleanest:

sql
SELECT DISTINCT salary
FROM (
  SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rk
  FROM employees
) t
WHERE rk = 2;

DENSE_RANK matters: with salaries 90, 90, 80, RANK labels 80 as rank 3 and returns nothing for rank 2. The classic window-free alternative:

sql
SELECT MAX(salary)
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);

It handles duplicates naturally and returns NULL when no second-highest exists. The LIMIT/OFFSET version (SELECT DISTINCT salary FROM employees ORDER BY salary DESC LIMIT 1 OFFSET 1) also works but returns an empty set rather than NULL when there is only one distinct salary — an edge case interviewers probe deliberately. Be ready to extend to 'Nth highest per department' by adding PARTITION BY department to the window.

IntermediateWhat is a window function and how is it different from GROUP BY?

GROUP BY collapses rows into one output row per group, and every selected column must be grouped or aggregated. A window function — an aggregate or ranking function with an OVER clause — computes over related rows while keeping every detail row, so you can show each order beside its customer's total: SUM(amount) OVER (PARTITION BY customer_id). PARTITION BY is the window analogue of GROUP BY; adding ORDER BY inside OVER makes the computation cumulative, and a frame clause (ROWS BETWEEN ...) defines sliding ranges for moving averages. Use GROUP BY when only the summary matters; use windows when you need detail and aggregate together — 'each employee's salary versus department average' in one pass, no self-join. Trap: window results cannot be filtered in WHERE; wrap in a subquery first.

IntermediateWhat is the difference between a CTE and a subquery?

Both produce an intermediate result for the outer query. A CTE (WITH clause) is named and defined up front, can be referenced several times in one statement, can chain — one CTE reading another — and is the only way to write recursive queries, walking org charts or graphs via WITH RECURSIVE. A subquery is inline: fine for one-off use, and the correlated form (referencing outer-query columns) has no CTE equivalent. The honest performance answer interviewers want: a CTE is primarily a readability tool, not an optimization — most engines inline CTEs exactly like subqueries. Some engines (PostgreSQL before v12, or with MATERIALIZED) evaluate a CTE once and reuse it, which can help repeated references or hurt by blocking predicate pushdown. Measure with EXPLAIN rather than assume.

IntermediateWhat is the difference between EXISTS and IN?

IN tests membership against a literal list or subquery result; EXISTS tests whether a correlated subquery returns at least one row, short-circuiting on the first match. The semantic difference that decides interviews is NULL handling: NOT IN against a subquery that returns even one NULL yields zero rows — every comparison collapses to UNKNOWN — while NOT EXISTS behaves as expected, so for anti-joins prefer NOT EXISTS or explicitly exclude NULLs. On performance, modern optimizers usually rewrite both into the same semi-join plan, so 'EXISTS is always faster' is folklore; the durable advice is that EXISTS states 'does a matching row exist' more directly and more safely. One more detail: SELECT * inside EXISTS is fine — the engine checks only row existence and never materializes the columns.

IntermediateWhat is a correlated subquery?

A correlated subquery references columns from the outer query, so logically it re-executes once per outer row — e.g. employees paid above their department average:

sql
WHERE salary > (SELECT AVG(salary) FROM employees e2 WHERE e2.dept_id = e1.dept_id)

It is the natural tool for per-row existence checks (EXISTS is almost always correlated) and compare-to-my-group logic. The cost model is the interview point: naively it is O(outer rows x inner scan), though optimizers often decorrelate it into a join, or the inner query stays cheap via an index. When slow, rewrite: compute per-group aggregates once in a CTE and join to it, or use a window — AVG(salary) OVER (PARTITION BY dept_id) yields the same comparison in a single pass. Showing that rewrite is worth more than reciting the definition.

IntermediateHow do you calculate a running total in SQL?

sql
SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)

A window aggregate whose frame grows row by row. Add PARTITION BY customer_id to restart the total per customer. The nuance that separates candidates: with ORDER BY alone the default frame is RANGE, not ROWS, and RANGE treats tied ORDER BY values as one unit — every row sharing a date shows the same running value, an apparent jump. Spelling out ROWS gives strict row-by-row accumulation; which is correct depends on whether ties should accumulate together, and per-row results are only deterministic when the ORDER BY is unique. Follow-ups to pre-empt: a moving average just changes the frame (ROWS BETWEEN 6 PRECEDING AND CURRENT ROW), and pre-window SQL required a self-join or correlated subquery.

IntermediateHow do you find and remove duplicate rows?

Find them with GROUP BY on the defining columns:

sql
SELECT email, COUNT(*)
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

Remove them while keeping one survivor per group with ROW_NUMBER() OVER (PARTITION BY email ORDER BY id), deleting rows where the number exceeds 1. In SQL Server you can DELETE from the CTE directly; in PostgreSQL or MySQL delete WHERE id IN (SELECT id FROM the numbered set WHERE rn > 1), since a CTE cannot be the delete target there. The ORDER BY inside the window is your keep policy — oldest id, or most recently updated — and interviewers like hearing that stated explicitly. Then say the part that shows judgment: deletion fixes the symptom; the cure is a UNIQUE constraint or unique index on the key so duplicates cannot return, added right after the cleanup.

IntermediateWhat is the difference between a clustered and a non-clustered index?

A clustered index defines the physical order of the table — the index's leaf level IS the row data — so each table can have exactly one. In SQL Server the primary key clusters by default, and MySQL's InnoDB always clusters on the primary key. A non-clustered index is a separate B-tree whose leaves hold key values plus a locator (a row pointer, or the clustering key), and you can create many. Consequences worth naming: range scans along the clustering key are fast; every secondary-index lookup on a clustered table takes an extra hop through the clustering key, so a wide primary key bloats every other index; and a covering non-clustered index (with INCLUDE columns) can answer a query without touching the table. The trade-off: every additional index taxes INSERT, UPDATE, DELETE and storage.

IntermediateWhat are LAG and LEAD used for?

LAG(col, n, default) reads the value n rows before the current row within the window's ordering; LEAD reads ahead. Both require OVER (ORDER BY ...), usually with PARTITION BY: month-over-month change is amount - LAG(amount) OVER (PARTITION BY product ORDER BY month). The optional third argument substitutes a default for NULL at partition edges — the first row has no predecessor, and forgetting that NULL silently drops the first period from delta calculations. Canonical uses: period-over-period comparisons; gap detection, where LEAD(date) minus date greater than 1 exposes missing entries; and sessionization — flag a new session when the gap since LAG(event_time) exceeds 30 minutes, then running-SUM the flags to number sessions. Pre-window SQL needed an awkward self-join on row position.

IntermediateWhat is the difference between DISTINCT and GROUP BY?

For a bare deduplication — SELECT DISTINCT city FROM users versus GROUP BY city — optimizers typically produce identical plans, so 'GROUP BY is faster' is not a real rule. The functional difference: GROUP BY defines groups you can aggregate over (COUNT, SUM, MAX per group), while DISTINCT only removes duplicate result rows. Use DISTINCT to state intent when no aggregate is needed; switch to GROUP BY the moment a count or sum enters. Related traps interviewers reach for: DISTINCT applies to the entire selected row, not just the column it visually sits beside; COUNT(DISTINCT col) combines both ideas and can be expensive; and selecting a column that is neither aggregated nor in the GROUP BY is an error in most engines — and was historically allowed but nondeterministic in older MySQL.

AdvancedWhat is database normalization and why does it matter?

Normalization stores each fact exactly once, eliminating redundancy and the anomalies it breeds: update anomalies (an address stored in five places, one missed), insert anomalies (no way to record a course with zero students), delete anomalies (removing the last enrolment erases the course). The ladder: 1NF — atomic values, no repeating groups; 2NF — no partial dependency on part of a composite key; 3NF — no transitive dependencies between non-key columns. Mnemonic: every attribute depends on the key, the whole key, and nothing but the key. The trade-off interviewers fish for: OLTP systems normalize to protect write integrity; analytics deliberately denormalizes into star schemas — fewer joins, faster scans — because redundancy is acceptable where data is written once and read many times.

AdvancedWhat are ACID properties in a database?

Atomicity: a transaction is all-or-nothing — if any statement fails, everything rolls back, so a transfer never debits without crediting. Consistency: a transaction moves the database from one valid state to another, honoring constraints, keys and triggers. Isolation: concurrent transactions behave as if run serially — in practice a spectrum of isolation levels (READ COMMITTED, REPEATABLE READ, SERIALIZABLE) trading anomalies such as dirty, non-repeatable and phantom reads against throughput. Durability: once committed, data survives crashes, typically via write-ahead logging. Interview depth lives in isolation: know your engine's default (PostgreSQL and SQL Server: READ COMMITTED; MySQL InnoDB: REPEATABLE READ) and contrast ACID with the BASE/eventual-consistency model of NoSQL stores.

AdvancedHow would you optimize a slow SQL query?

Start with evidence: EXPLAIN (ANALYZE where available) shows the actual plan — look for full scans on large tables, misestimated row counts, and nested-loop joins fed huge inputs. Then, roughly in order of payoff: add or fix indexes on WHERE, JOIN and ORDER BY columns — composite-index column order matters (equality before range); make predicates sargable — WHERE YEAR(created_at) = 2026 defeats an index while a half-open date range uses it, as do leading-wildcard LIKEs and implicit casts; select only needed columns so a covering index can serve the query; replace correlated subqueries with joins or window functions; refresh statistics. Finally ask whether the real fix is upstream — pagination, caching, or a precomputed materialized aggregate — and re-measure after every change.

AdvancedHow do you pivot rows into columns in SQL?

The portable technique is conditional aggregation — one expression per target column, grouped by the row key:

sql
SELECT product,
       SUM(CASE WHEN quarter = 'Q1' THEN revenue END) AS q1,
       SUM(CASE WHEN quarter = 'Q2' THEN revenue END) AS q2
FROM sales
GROUP BY product;

CASE without ELSE yields NULL, which aggregates ignore, so each SUM sees only its own slice; COUNT or MAX work the same way, and COALESCE(..., 0) turns the NULLs into zeros. SQL Server and Oracle offer a PIVOT operator and PostgreSQL has crosstab(), but all share one limitation worth stating unprompted: output columns must be known when the query is written — a dynamic column count requires dynamically building the SQL. Mentioning the reverse operation, UNPIVOT (columns back into rows), pre-empts the standard follow-up.

AdvancedWhat is the difference between a materialized view and a regular view?

A regular view is a stored query: it holds no data, runs (or is merged) each time it is referenced, and always reflects live data. A materialized view physically stores the query's result, so reads are fast — often index-backed — but the data is only as fresh as its last refresh. Refresh is the interview substance: complete (recompute all) or incremental where the engine supports change tracking, scheduled or on-commit — each trades staleness against load. Use regular views to encapsulate logic and control column-level access; use materialized views to precompute expensive joins and aggregates for dashboards. Classic follow-up: PostgreSQL needs REFRESH MATERIALIZED VIEW (CONCURRENTLY to avoid blocking readers); SQL Server's analogue is an indexed view, maintained automatically.

By company

Data Analyst interviews that ask these questions

See how this topic shows up in real Data Analyst loops — rounds, difficulty and company-specific questions:

All company interview guides →

FAQ

Common Questions

Which SQL topics matter most for interviews?

Joins, grouping, CTEs, subqueries, window functions, date logic, case statements, and business metric interpretation matter most.

How many SQL questions should I practice?

Practice fewer topics deeply. Make sure you can explain why the query is correct, not only write memorized syntax.

Are window functions required?

For analyst, analytics engineer, and data engineer roles, window functions are very common and worth practicing seriously.

How do I handle business case SQL questions?

Clarify the metric, define the grain, handle edge cases, then write the query. Interviewers value assumptions.

Should I optimize every SQL answer?

First make it correct and readable. Then discuss filters, selected columns, join keys, partitions, and indexes if asked.

What SQL interview questions are asked for experienced candidates?

At three to five years the questions are situations, not syntax: a query that got slow overnight, an upsert that was run twice, a report that disagrees with finance, a table that needs history. Good answers name what you would check first (row counts, partition pruning, the EXPLAIN plan), the command that proves it, and the trade-off of the fix. Expect isolation levels, MERGE semantics, SCD type 2 versus snapshots, and index design with the columns named in order.

Which SQL joins questions are most common?

Four: the row count each join type returns on a small dataset, customers with no orders (LEFT JOIN with IS NULL or NOT EXISTS), why a LEFT JOIN returned more rows than the left table (duplicate keys on the right side), and a self join of employees to managers. The follow-up is nearly always which condition belongs in ON rather than WHERE.

What is the difference between a primary key and a unique key?

Both stop duplicate values. A table has one primary key, it can never be NULL, and it is the row's identity that foreign keys usually point to. A table can have several unique keys, and a unique column can hold NULLs (most databases allow more than one NULL; SQL Server allows one). Declaring either one creates a unique index.

What is the difference between SQL and NoSQL databases?

SQL databases such as MySQL, PostgreSQL and SQL Server store data in tables with a fixed schema, enforce relationships with keys and transactions, and are queried with SQL. NoSQL databases such as MongoDB, Cassandra and Redis store documents, wide columns or key-value pairs, usually with a flexible schema, and are built to scale out across machines. Data teams meet both; analyst and data engineering interviews test SQL.

How can PrepNPlaced help with SQL practice?

Use AI Mock Interview for role-aware SQL practice and Open Learning or courses for structured SQL revision.

Next Step

Turn The Guide Into Practice

Use PrepNPlaced tools to turn this learning path into resume proof, targeted practice, and interview-ready explanations.

Practice AI Mock Interview