SQL (PostgreSQL, MySQL, SQLite) Module 22
SQL interview preparation: questions
Questions on SQL interview preparation: short warm-ups, then what each harder question tests, the first step of a good answer and its follow-ups.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher to mid
What should you ask before you write the query in an SQL interview?
Answered in the lesson How to approach SQL interview questions
-
Fresher to mid
What is the denominator of a cohort's month-1 retention, and what is the denominator of a month's churn rate?
Month-1 retention divides by the cohort's size: every user whose first activity fell in the cohort month, whether or not they come back. Its numerator is the cohort's users who were active in the next calendar month, each counted once. A month's churn rate divides by the users active in the previous month, whatever their cohort, and its numerator is those of them who were not active this month. The two are not complements in general: retention follows one cohort, churn follows everyone active the month before. In the lesson's data the January cohort's month-1 retention is 3 of 5 users, 60.0 %, and February's churn is 2 of January's 5 active users, 40.0 %; they add up to 100 % only because January's active users are exactly its cohort. In March, 3 of February's 7 active users churned: 42.9 %.
From the lesson Interview drills: retention, growth, rolling
-
Fresher to mid
What is the difference between DELETE, TRUNCATE and DROP?
Answered in the lesson SQL theory questions and output prediction
-
Fresher
Why is NULL = NULL not true, and how do you test for NULL?
Answered in the lesson SQL theory questions and output prediction
-
Fresher to mid
In SQL terms, how do "applied to every drive of a recruiter", "applied only to that recruiter's drives" and "never applied to that recruiter" differ?
"Every" is relational division: the student needs an application for each of the recruiter's drives, so you check that no drive is missing, with a double NOT EXISTS or by comparing the student's count of distinct drives of that recruiter with the recruiter's number of drives. "Only" looks at the student's own applications instead: there must be at least one, and none may belong to another recruiter, so a student can qualify while missing some drives. "Never" is an anti-join: no application to any of the recruiter's drives, and students with no application at all qualify too. Write it with NOT EXISTS, because NOT IN returns nothing when its list holds a NULL. In the lesson's data, Aarav and Charan applied to every Kaveri drive, Bhavna and Charan only to Kaveri drives, and Esha and Farid never applied to one.
From the lesson Interview drills: 'all', 'none' and division
-
Fresher to mid
Why is the overlap test for two half-open ranges a.start < b.end AND b.start < a.end, and what does it say about ranges that only touch?
Two ranges fail to overlap in exactly two ways: one ends before or when the other starts, a.end <= b.start, or the other way round, b.end <= a.start. Negating "one or the other" gives "neither", so they overlap when a.end > b.start and b.end > a.start, which is the same as a.start < b.end AND b.start < a.end. Those two comparisons cover every case: partial overlap, one range inside the other and identical ranges. A half-open range includes its start and excludes its end, so a booking from 9:00 to 13:00 and one from 13:00 to 14:00 only touch: 13:00 < 13:00 is false and they do not overlap, which is what a hall's calendar wants. A test with <=, or BETWEEN, would flag them as a clash.
From the lesson Interview drills: intervals and as-of lookups
-
Fresher to mid
What is the grain of a report, and why should you state it before you write the query?
The grain is what one row of the result stands for: one row per city, per city and month, per customer. Stating it first fixes the GROUP BY, and it tells you how every source table must be reduced before the tables meet. A join changes the grain of whatever it joins: orders joined to their payments have one row per payment, so an order paid in two parts is counted twice and its delivery fee added twice. So aggregate each table at its own grain, orders per city and payments per city, and join the summaries. The grain also says which groups must appear: a report with one row per city should list every city, so it starts from the cities table with a LEFT JOIN, and a city without orders shows 0 orders and no shares.
From the lesson Interview drills: reports, pivots, percentages
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to mid
An interviewer says only: "Write a query that finds our most loyal customers." The orders table has id, customer_id, placed_at, amount and status. What do you ask before writing SQL, and how do you turn the answers into a query you can test?
- What it tests
- Turning a vague request into a definition with a grain and a measure, asking about statuses, NULLs, ties and time, and testing a query against sample rows.
- First step
- Offer two or three definitions instead of an open question: the most orders in the last 12 months, the most months with an order, or the highest spend. Ask which one fits, then which statuses count and how many customers to return when two tie at the cut-off.
- Follow-ups
-
- The interviewer says "you decide". What do you choose, and why?
- How do you show that your query handles a tie at the cut-off?
- The table holds 200 million orders and the query runs every night. What changes?
From the lesson How to approach SQL interview questions
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Fresher to mid
Your take-home query returns the right numbers on your own sample data, but the automated checker marks it wrong, and you cannot see the checker's data. What do you check, and in what order, before you change the logic?
- What it tests
- How result sets are compared, the details that make two equal answers differ, engine differences, and testing for edge cases your sample did not contain.
- First step
- Start with the causes that need no change to the logic: the column names and their order, the row order the task asks for, the rounding and types of computed values, and whether empty groups should appear with 0 or not at all. Then ask which engine the checker runs.
- Follow-ups
-
- The task says "return the result in any order". What still has to match?
- Your answer shows 33.33 where the sample output shows 33.3. How do you decide?
- How do you build test data for the edge cases you suspect?
From the lesson How to approach SQL interview questions
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
Every month a dashboard shows 0 % month-1 retention for the newest cohort, and the product team asks why the latest release failed. What is wrong with the query, and what should the dashboard show for that cohort instead?
- What it tests
- Censored cohorts, the difference between an unknown value and zero, and saying when a metric becomes final.
- First step
- Ask on which day the data ends, then check whether the newest cohort's month 1 has started. If it has not, every user of the cohort counts as not retained only because the month has not happened yet.
- Follow-ups
-
- The data ends in the middle of a month. Which cohorts are final?
- Product wants a number for the newest cohort anyway. What can you offer?
- How do you stop this bug from coming back?
From the lesson Interview drills: retention, growth, rolling
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Monthly active users grew 8 % last month, yet month-1 retention fell for each of the last three cohorts. How can both be true, and what query shows where the extra active users came from?
- What it tests
- Splitting active users into new, retained and resurrected, counting churn, and the identity that ties the four counts together.
- First step
- Label each active user of the month as new, retained from the month before or resurrected after a gap, and count the users of the month before who churned. The change in active users equals new plus resurrected minus churned, so new users can carry growth while retention falls.
- Follow-ups
-
- Write the query for the four counts.
- Resurrected users doubled in one month. What do you check first?
- How would you show all of this on one chart?
From the lesson Interview drills: retention, growth, rolling
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
A payments table covers 2 million merchants over three years, and most merchants have no payment on most days. Finance wants each merchant's 7-day rolling revenue for every day. What do you build, and what does each option cost?
- What it tests
- Estimating a dense calendar result, sparse range frames, summary tables, late corrections and the indexes that serve each option.
- First step
- Estimate the dense result first: 2 million merchants × 1,096 days is about 2.2 billion rows, nearly all zeros. Then ask who reads it, how often and for which days, before choosing between a full calendar, a sparse range frame and a daily summary table.
- Follow-ups
-
- Only yesterday's values are needed each morning. What changes?
- A refund arrives for a payment made 20 days ago. What must be recomputed?
- Which index or partitioning serves the sparse version?
From the lesson Interview drills: retention, growth, rolling
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
On a staging copy you run BEGIN, then TRUNCATE orders, then ROLLBACK, once on PostgreSQL 18 and once on MySQL 8.4. What is left in orders in each engine, what id does the next new order get, and when would you choose DELETE instead?
- What it tests
- How TRUNCATE behaves inside a transaction and with identity counters in each engine, its triggers, foreign keys and locks, and choosing between DELETE, TRUNCATE and DROP.
- First step
- Separate the engines at once. PostgreSQL treats TRUNCATE as part of the transaction, so the rollback restores every row; MySQL commits implicitly around TRUNCATE, so the ROLLBACK has nothing left to undo. Then ask about the identity column, triggers and foreign keys.
- Follow-ups
-
- Another table has a foreign key to orders. What does each engine do?
- Which of the three statements fires the audit triggers on orders?
- The table holds 300 million rows and must stay readable while it is emptied. What do you run?
From the lesson SQL theory questions and output prediction
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A query lists the students who applied to every drive of recruiter 7 with HAVING COUNT() = (SELECT COUNT() FROM drives WHERE recruiter_id = 7). It returns one student who skipped a drive and misses another who applied to all of them. Explain both, and fix the query.
- What it tests
- Relational division by counting, duplicates, filtering to the divisor before grouping, and test data that breaks the query.
- First step
- Look at the two students' applications. The one who skipped a drive probably applied to another drive twice, and the one who vanished applied to some drive twice or to another recruiter's drive: COUNT(*) counts rows, not different drives of recruiter 7.
- Follow-ups
-
- Write it without COUNT at all.
- Applications to every recruiter share one table. Where must they be filtered?
- What test data proves the fix?
From the lesson Interview drills: 'all', 'none' and division
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Three queries for "students who applied to every drive of a recruiter", a count, a double NOT EXISTS and an EXCEPT, agree for every recruiter except a new one with no drives. What does each return there, which answer is right, and how do you make the query say so?
- What it tests
- Vacuous truth, how each form of division treats an empty divisor, and turning an unstated business rule into a guard.
- First step
- Evaluate each form on an empty set of drives. The double NOT EXISTS and the EXCEPT find no missing pair, so they return every student; the count has no application to group, so it returns nobody. Then ask what the report should say for a recruiter without drives.
- Follow-ups
-
- Which answer would you choose for a recruiter's shortlist, and why?
- How do you write the guard so that it costs almost nothing?
- The drives come from a parameter list instead of a table. What changes?
From the lesson Interview drills: 'all', 'none' and division
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A nightly job emails the students who never applied to any drive of a recruiter, chosen with WHERE id NOT IN (subquery). One night it sends no email at all, and nobody notices for a week. What happened, how do you confirm it quickly, and how do you make it impossible?
- What it tests
- NOT IN with NULLs, three-valued logic, diagnosing a silent empty result, and guarding a job against it.
- First step
- Suspect the subquery before the data volume: if its column can be NULL, one NULL makes NOT IN NULL for every student. Count the NULLs the subquery returned that night, and compare NOT IN with NOT EXISTS on the same data.
- Follow-ups
-
- Why does one NULL change the answer for every student?
- What would have caught it on the first night?
- Is NOT IN ever safe to use?
From the lesson Interview drills: 'all', 'none' and division
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A query merges each hall's bookings into busy blocks with LAG(ends_at) and a running SUM of "new block" flags. A conference booked from 9:00 to 13:00, with two short meetings inside it, comes out as three blocks. Find the bug, fix it, and show why the fix is correct.
- What it tests
- The islands technique on intervals, a running maximum against the previous row, and reasoning about sorted intervals.
- First step
- Write the three bookings in start order with LAG(ends_at) beside each. The second meeting starts after the first meeting ends but before the conference ends, so comparing with the previous row's end alone cannot see the conference.
- Follow-ups
-
- Do bookings that only touch belong to the same block? Where is that decided?
- Two bookings start at the same minute. Does your query still work?
- How would you do the same merge in PostgreSQL without window functions?
From the lesson Interview drills: intervals and as-of lookups
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
You compute the peak number of concurrent video calls per day from 40 million call records with start and end times. Your peak is one higher than the billing system's on most days. What could explain the difference, and how do you settle which number is right?
- What it tests
- The +1 and -1 event method, ties at the same instant, closed against half-open intervals, time zones and the day boundary, and reconciling two systems.
- First step
- Find one day where the numbers differ and the moment of your peak. Check whether calls ending at that instant were still counted while new ones started: if starts sort before ends at the same timestamp, the peak is one too high for half-open intervals.
- Follow-ups
-
- Which tie order is right, and why?
- A call starts before midnight and ends after it. Which days count it?
- How long does the query take on 40 million calls, and what makes it faster?
From the lesson Interview drills: intervals and as-of lookups
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Each of 200 million order lines needs the product's price in force when its order was placed, from a price history of 3 million changes. Compare a correlated subquery, LATERAL and a join on validity ranges, and say which you would run and with which index.
- What it tests
- As-of joins, validity ranges built with LEAD, index-supported searches against sort-based joins, and edge cases such as a change at the same instant.
- First step
- Write the rule first: the latest change at or before the order's time, for the same product. Then estimate each plan: one index search per line, a range join on precomputed validity ranges, or one sort of lines and changes together, and ask whether the query runs once or every night.
- Follow-ups
-
- An order was placed before the product's first price. What should the line show?
- A price change and an order share the same timestamp. Which price applies?
- The history is corrected after the fact. What has to be recomputed?
From the lesson Interview drills: intervals and as-of lookups
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
List every employee with their manager's name from employees(id, name, manager_id). The company has one employee at the top, and after an HR import some manager_id values point at employees who are no longer in the table. Write it, then say how many rows each version returns.
- What it tests
- Self-joins with aliases, inner against outer joins, telling a NULL key from a broken key, and finding orphaned references.
- First step
- Join the table to itself under two aliases and keep every employee: LEFT JOIN employees AS m ON m.id = e.manager_id. Then separate the two reasons a manager name can be absent with a CASE on e.manager_id.
- Follow-ups
-
- How do you list only the rows whose manager_id points at nobody?
- What stops those rows from being written in the first place?
- The report must also show the manager's manager. What do you add?
From the lesson Org chart drills: self-joins and recursion
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A dashboard needs, for every manager in a 200,000-employee org chart about twelve levels deep, the number of employees below them at any depth and their total salary. Write the query and say what it costs.
- What it tests
- Recursive walks from every row, removing the starting row from the counts, the index the step needs, and when a closure table beats a query.
- First step
- Pair every employee with themselves in the anchor, then add everyone below in the recursive step, so one query answers all managers at once. Group by the starting employee and subtract that employee's own row and salary.
- Follow-ups
-
- Why does the count need a minus one?
- Which index does the recursive step need, and what happens without it?
- The dashboard must load in under a second. What do you change?
From the lesson Org chart drills: self-joins and recursion
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
After an HR import, the query that shows each employee's chain of managers hangs on PostgreSQL and fails on MySQL with "Recursive query aborted after 1001 iterations". Explain both, fix the query, and find the bad data.
- What it tests
- Cycles in a self-referencing table, guarding a recursive walk, engine differences in recursion limits, and a data check that locates the loop.
- First step
- Two employees almost certainly manage each other, so the walk alternates between them for ever. PostgreSQL has no default limit and keeps going; MySQL stops at cte_max_recursion_depth, 1,000 by default, and raises error 3636.
- Follow-ups
-
- Which guard would you add to the recursive step, and why not raise the limit?
- How do you find every employee caught in a loop?
- What would have prevented the import from writing the loop?
From the lesson Org chart drills: self-joins and recursion
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Fresher to mid
A city-wise payment report shows more orders for one city than the orders table holds for it, and its delivery fees are too high. The query joins orders to payments and then groups by city. What happened, and how do you fix the counts, the fees and the shares?
- What it tests
- Fan-out from a one-to-many join, counting and summing at the right grain, and checking a report against its source tables.
- First step
- Count the city's orders straight from the orders table and compare with the report. Then look for orders with more than one payment: after the join each payment is a row, so those orders are counted and their fees added once per payment.
- Follow-ups
-
- Does COUNT(DISTINCT order_id) fix everything?
- Which totals in the report are still right, and why?
- How do you test the report so this cannot come back?
From the lesson Interview drills: reports, pivots, percentages
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
The same report query shows a share of 33 in PostgreSQL, 33.3333 in MySQL and 33.3 in SQLite after a rewrite, and some rounded totals differ by 0.01 between the engines. Explain each difference and make the query return the same numbers in all three.
- What it tests
- Integer division in each engine, MySQL's division scale, exact against approximate types, and each engine's rounding rule for halves.
- First step
- Look at the types before the values: an integer divided by an integer is truncated in PostgreSQL and SQLite and is a decimal in MySQL. Then check which columns are REAL or DOUBLE, because halves of approximate numbers round differently from exact ones.
- Follow-ups
-
- Why does round(2.5) give 3 in one place and 2 in another?
- How should money be stored so that reports agree?
- Where in the query should the rounding happen?
From the lesson Interview drills: reports, pivots, percentages
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Fresher to mid
On an assessment that runs Oracle 11g, a candidate writes SELECT salary FROM emp WHERE ROWNUM = 2 ORDER BY salary DESC for the second highest salary and gets no rows. Explain why, and write a version that works on 11g and returns one row holding NULL when there is no second salary.
- What it tests
- When Oracle assigns ROWNUM, the top-N pattern with a sorted inline view, NULL ordering, and choosing syntax by the release under test.
- First step
- Say when ROWNUM is assigned: as each row is fetched, before ORDER BY. Then trace the first two rows through ROWNUM = 2 and notice that neither can ever be numbered 2.
- Follow-ups
-
- Why filter out NULL salaries first?
- Write the same on Oracle 19c.
- How would DENSE_RANK() do it, and when is it the better choice?
From the lesson Interview drills: Oracle SQL syntax
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A CONNECT BY report of the org chart must leave out one department head and everyone below them. The developer adds WHERE emp_id <> 120: the head disappears, but the head's reports are still listed. Explain why, and say where the condition belongs, in CONNECT BY and in a recursive WITH.
- What it tests
- The order in which Oracle evaluates a hierarchical query, pruning against filtering, and the same distinction in recursive WITH.
- First step
- Recall the order Oracle uses: the hierarchy is built from START WITH and CONNECT BY first, and a WHERE without a join is applied afterwards to each row on its own.
- Follow-ups
-
- What if the head is a root of the tree?
- How do you keep the head but drop the reports?
- What happens if the data contains a loop?
From the lesson Interview drills: Oracle SQL syntax
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
Delete duplicate customers in Oracle 19c, keeping the oldest row per email and ignoring case. A WITH ... DELETE from another engine fails, and so does DELETE FROM (SELECT ..., ROW_NUMBER() ... ) WHERE rn > 1. Explain both failures and write the delete you would run.
- What it tests
- Oracle's DELETE syntax and its rules for deletable views, ROWID, deterministic ordering, and running a destructive statement safely.
- First step
- Look at what each statement deletes from: a CTE that DELETE cannot begin with, and an inline view whose analytic function makes it impossible to delete from. Then find something that names the rows directly.
- Follow-ups
-
- Child tables reference some duplicates. What do you do first?
- How do you stop new duplicates?
- Does Oracle 26ai change the answer?
From the lesson Interview drills: Oracle SQL syntax
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
All SQL (PostgreSQL, MySQL, SQLite) interview questions · The SQL (PostgreSQL, MySQL, SQLite) track