Top 50 SQL Interview Questions and Answers (2026)

The 50 SQL interview questions and answers that matter in 2026, runnable on PostgreSQL: joins, window functions, CTEs, and the NULL traps that fail screens.

Topics: sql, interview questions, data science, data engineering, 2026

Author: PracHub Team

Published: 4/9/2026

Top 50 SQL Interview Questions and Answers (2026)

April 9, 2026
29 min read

Quick Overview

A checklist of the 50 SQL interview questions data analysts, data scientists, and analytics engineers actually face, grouped by topic and difficulty. Every worked answer runs on PostgreSQL as written, with sample data and results inlined, and the hardest patterns are grounded in real questions asked at Meta, DoorDash, TikTok, and OpenAI.

sqlinterview questionsdata sciencedata engineering2026
Data ScientistFree

SQL interviews reward pattern recognition, not trivia. Nearly every question you will face is a variation on five families: filtering with correct NULL logic, joining tables, aggregating with GROUP BY and CASE, ranking and comparing rows with window functions, and decomposing multi-step logic into CTEs. This page lists the 50 questions that come up most often for data analyst, data scientist, and analytics engineer screens, grouped by topic and ordered by difficulty, with runnable PostgreSQL answers for the patterns interviewers lean on hardest.

Use it as a checklist. If you can solve every category below from a blank editor, you are ready.

Key Takeaways

  • Joins and window functions decide most SQL screens. If you are short on time, drill those two families first and skip the rest.
  • "Who is missing" always means LEFT JOIN ... IS NULL or NOT EXISTS — never NOT IN, which silently returns zero rows the moment the subquery actually produces a NULL.
  • ROW_NUMBER, RANK, and DENSE_RANK only differ when ties appear. Interviewers create ties on purpose to see whether you chose deliberately.
  • NULL handling is scored even when the question never mentions it: guard divisions with NULLIF, compare with IS DISTINCT FROM, and remember aggregates skip NULLs except COUNT(*).
  • Every query below runs on PostgreSQL as written, with sample data inlined where results are shown. Read the answer, close the page, then re-derive it. Recognition is not recall.

top 50 sql interview questions with answers 2026

How to use this list

The 50 questions are grouped into five sections: basics and NULL logic (1–8), joins and set operations (9–18), aggregation and CASE (19–28), window functions (29–40), and CTEs plus DML and optimization (41–50). Each section opens with the full question list, then works through the answers that carry the most weight in real screens — several of them tied to questions asked at Meta, DoorDash, TikTok, and OpenAI, pulled from PracHub's question bank.

If analytics SQL is newer to you, the six core patterns in SQL for Data Analysis are the gentler on-ramp; come back here once GROUP BY feels automatic.

Map the question to a pattern first

Before you type, name the family. Most prompts announce themselves once you know the tells.

Question signalLikely patternFirst move
"Who has never / who is missing"LEFT JOIN + IS NULLOuter join, filter on the null side
"Top N per group", "rank within"Window functionROW_NUMBER/RANK with PARTITION BY
"Compared to previous/next period"Window functionLAG/LEAD over an ordered set
"Running total", "moving average"Window functionSUM/AVG OVER (ORDER BY ...)
"Step by step", "then using that..."CTEChain named WITH blocks
"Hierarchy", "longest streak"Recursive CTE / gaps-and-islandsAnchor + recursive member
"Per group totals with a condition"Aggregation + HAVINGGROUP BY then filter groups
"Pivot rows into columns"Conditional aggregationSUM(CASE WHEN ...)

Freshers: start with the basic SQL questions, Q1–28

If this is your first analyst or data-engineering screen, the fresher set is Q1–28: basics and NULL logic (1–8), joins and set operations (9–18), aggregation and CASE (19–28). Entry-level screens rarely ask for a window function. They ask you to filter with the right operator, join two or three tables without inflating the row count, and group with a HAVING clause that filters groups rather than rows.

What gets scored is often not the query. Whether you notice NULLs comes first: = NULL matching nothing, and NOT IN collapsing to zero rows, are the two traps that show up in almost every entry-level screen. Then whether you reach for LEFT JOIN on a "who has never" prompt instead of an inner join. Then whether you say the output grain out loud — one row per customer, per month, per what.

Work Q1–28 in order before touching the rest. If joins still take effort, SQL Joins walks every join type with exact result sets, and SQL GROUP BY, Aggregate Functions, and HAVING covers the aggregation half. For an honest sense of the runway, How Long Does It Take to Learn SQL? puts hours on it.

For experienced candidates: Q29–50 is where the screen is decided

With a few years of SQL behind you, the first 28 questions are a warm-up you should clear without thinking. The decision happens in Q29–50: window functions (29–40), then CTEs, DML, and optimization (41–50).

Senior screens probe past correctness. Deliberate choice: not "does RANK work" but why DENSE_RANK rather than ROW_NUMBER given the ties in the data, and what a missing tiebreaker does to a "latest row" query. Frame awareness: ROWS versus RANGE, why LAST_VALUE under the default frame stops at the current row's peers instead of the partition's last row, whether a 7-day moving average spans 7 rows or 7 calendar days. Then debugging, where you are handed someone else's wrong query and asked which stage lost the rows.

Expect at least one multi-step problem stated in a single sentence and left for you to decompose: cohort the users, order each user's events, aggregate per group. Chaining named CTEs is what keeps that legible while you narrate it. If the frame-clause details read as new, the window functions guide builds them properly, and SQL Order of Operations is why you cannot filter on a window function in WHERE.

Questions 1–8: Basics, filtering, and NULL logic

Screens for junior analyst roles often start and end here. Senior screens skip the syntax but still probe the NULL semantics, because that is where production bugs live.

  1. Select the top 10 rows sorted by a column (ORDER BY + LIMIT)
  2. Filter rows on multiple conditions with AND, OR, and correct parenthesization
  3. Explain <> vs !=, and why neither ever matches a NULL
  4. Find rows where a column is NULL (and why = NULL returns nothing)
  5. Replace NULLs with a default using COALESCE
  6. Deduplicate values: DISTINCT vs GROUP BY
  7. Explain the difference between WHERE and HAVING
  8. Extract parts of a date: date_trunc, EXTRACT, and grouping by month

Worked answer (Q3–Q4): the NULL comparison trap. Any comparison with NULL evaluates to unknown, and WHERE drops unknown rows. Watch what happens with this table:

WITH products(product_id, discount) AS (
  VALUES (1, 10), (2, 15), (3, NULL)
)
SELECT product_id
FROM products
WHERE discount <> 10;
product_id
2

Product 3 vanished. NULL <> 10 is unknown, not true, so the row is filtered out even though its discount is plainly not 10. If you want NULLs to count as "not equal", say so explicitly:

WITH products(product_id, discount) AS (
  VALUES (1, 10), (2, 15), (3, NULL)
)
SELECT product_id
FROM products
WHERE discount IS DISTINCT FROM 10;
product_id
2
3

The <> vs != half of the question is a softball (they are identical in every major engine; <> is the standard spelling), but the NULL behavior behind it fails real candidates. The full set of traps, including how NOT IN inherits the same problem, is worked through in SQL Not Equal: <> vs !=, and the NULL Traps That Follow.

Worked answer (Q5): defaulting NULLs. COALESCE returns its first non-NULL argument, which makes it the standard tool for both display defaults and NULL-safe math:

SELECT
  order_id,
  COALESCE(discount, 0) AS discount_applied,
  price * (1 - COALESCE(discount, 0) / 100.0) AS final_price
FROM orders;

Two details interviewers listen for: COALESCE short-circuits left to right, and it is not the same as NULLIF, which goes the other direction (turns a value into NULL). Engine-specific variants and the classic mistakes are covered in SQL COALESCE: Syntax, Engine Differences, and the NULL Traps Interviewers Test.

Questions 9–18: Joins and set operations

These are table stakes. If you cannot write a LEFT JOIN without pausing, drill here until it is automatic, because a stumble on joins ends screens faster than anything else.

  1. Find all customers who have never placed an order (LEFT JOIN + NULL check)
  2. Explain INNER vs LEFT vs FULL OUTER join: which rows survive each one
  3. Join users and orders to compute total spend per user
  4. Self-join: find all pairs of employees in the same department
  5. Find orders placed within 7 days of signup (date-range join)
  6. Find customers who ordered in both January and February
  7. Explain UNION vs UNION ALL, including what "duplicate" means for NULLs
  8. Anti-join correctly: NOT EXISTS vs NOT IN and the NULL trap
  9. Join three tables and keep the row count under control (fan-out)
  10. Show each product with its most recent order date

Worked answer (Q9): customers with no orders. Outer-join, then keep only the rows where the right side failed to match:

WITH customers(customer_id, name) AS (
  VALUES (1, 'Ana'), (2, 'Ben'), (3, 'Chen')
),
orders(order_id, customer_id) AS (
  VALUES (101, 1), (102, 1), (103, 3)
)
SELECT c.customer_id, c.name
FROM customers AS c
LEFT JOIN orders AS o
  ON o.customer_id = c.customer_id
WHERE o.customer_id IS NULL;
customer_idname
2Ben

An INNER JOIN can never answer a "who is missing" question. The missing rows have nothing to match, so an inner join discards exactly the rows you were asked to find.

Worked answer (Q16): why NOT IN betrays you. The tempting alternative to the query above is NOT IN, and it breaks the moment the subquery can produce a NULL:

WITH customers(customer_id) AS (
  VALUES (1), (2), (3)
),
orders(customer_id) AS (
  VALUES (1), (3), (NULL)
)
SELECT customer_id
FROM customers
WHERE customer_id NOT IN (SELECT customer_id FROM orders);

This returns zero rows. For customer 2, the predicate expands to 2 <> 1 AND 2 <> 3 AND 2 <> NULL; that last comparison is unknown, which poisons the whole conjunction. NOT EXISTS has no such failure mode and is what you should reach for by default:

WITH customers(customer_id) AS (
  VALUES (1), (2), (3)
),
orders(customer_id) AS (
  VALUES (1), (3), (NULL)
)
SELECT c.customer_id
FROM customers AS c
WHERE NOT EXISTS (
  SELECT 1 FROM orders AS o WHERE o.customer_id = c.customer_id
);
customer_id
2

Worked answer (Q14): active in both months. Aggregate per customer and count distinct months, rather than joining the table to itself:

SELECT customer_id
FROM orders
WHERE order_date >= DATE '2026-01-01'
  AND order_date <  DATE '2026-03-01'
GROUP BY customer_id
HAVING COUNT(DISTINCT date_trunc('month', order_date)) = 2;

The half-open date range (>= start, < next period) is a habit worth showing off: it works identically for dates and timestamps and never drops the last day of the month.

Q15 in one paragraph. UNION deduplicates the combined result set; UNION ALL keeps everything and is much cheaper because no dedup sort or hash is needed. Two wrinkles catch candidates: UNION treats two NULLs as duplicates of each other (unlike =), and dedup applies across the entire row, not one column. When the inputs cannot overlap, say "these are disjoint, so UNION ALL is correct and faster" — that sentence alone is a senior signal. The full semantics are in UNION vs UNION ALL in SQL: Dedup Semantics, NULL Rules, and the Interview Traps.

Questions 19–28: Aggregation and CASE

Basic GROUP BY is assumed. What actually gets tested is HAVING, conditional aggregation, and whether you understand the grain of the table you are producing.

  1. Find the top 5 customers by total order value
  2. Count unique products ordered per month
  3. Departments with more than 10 staff and average salary above 100k (HAVING)
  4. Conditional counts: how many completed vs refunded orders per region
  5. Pivot rows into columns without a PIVOT clause
  6. Find the mode (most frequent value) of a column
  7. Calculate the median without a built-in median function
  8. Average order value excluding the top 1% as outliers
  9. Group by multiple dimensions: category, region, and month at once
  10. Find users who took at least 3 actions in a single day

Worked answer (Q21): filtering groups. WHERE filters rows before grouping; HAVING filters groups after aggregation. Mixing them up is the single most common conceptual miss:

SELECT
  department_id,
  COUNT(*)    AS headcount,
  AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 10
   AND AVG(salary) > 100000;

Worked answer (Q22–Q23): conditional aggregation, the pivot in disguise. Wrap a CASE inside an aggregate and rows become columns with no special syntax:

WITH orders(region, status, amount) AS (
  VALUES ('NA', 'completed', 100), ('NA', 'refunded', 40),
         ('EU', 'completed',  80), ('EU', 'completed', 20)
)
SELECT
  region,
  SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END) AS completed_revenue,
  SUM(CASE WHEN status = 'refunded'  THEN amount ELSE 0 END) AS refunded_revenue,
  COUNT(*) FILTER (WHERE status = 'completed')               AS completed_orders
FROM orders
GROUP BY region
ORDER BY region;
regioncompleted_revenuerefunded_revenuecompleted_orders
EU10002
NA100401

COUNT(*) FILTER (WHERE ...) is the cleaner Postgres form; fall back to SUM(CASE WHEN ... THEN 1 ELSE 0 END) in dialects without FILTER. Conditional counts, ratio-of-counts, and the ELSE-clause traps that follow are worked through in SQL CASE WHEN: Syntax, Conditional Counts, and the Interview Patterns That Matter.

Worked answer (Q25): median. In PostgreSQL the direct route is an ordered-set aggregate:

SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY amount) AS median_amount
FROM orders;

If the interviewer bans percentile functions (they often do, to test window fluency), number the rows from both ends and keep the middle: rows where ROW_NUMBER ascending and descending differ by at most 1, averaged. Sketch that verbally before writing it; the idea matters more than the exact code.

Worked answer (Q28): threshold per group. The grain is (user, day), so group by exactly that:

SELECT user_id, event_date, COUNT(*) AS actions
FROM events
GROUP BY user_id, event_date
HAVING COUNT(*) >= 3;

Saying "the output grain is one row per user per qualifying day" out loud is worth more than the query itself. Fan-out bugs and double-counting almost always trace back to an unexamined grain.

Questions 29–40: Window functions

Window functions separate junior from senior candidates more reliably than any other topic. They compute a per-row value relative to a group without collapsing rows, which is exactly what rankings, comparisons to previous periods, and dedup all require. If this section feels shaky, the window functions guide builds it from the ground up.

  1. Rank employees by salary within their department
  2. Explain ROW_NUMBER vs RANK vs DENSE_RANK (ties!)
  3. Find the top 3 products by revenue in each category
  4. Calculate a running total of sales by date
  5. Calculate the 7-day moving average of daily active users
  6. Month-over-month revenue growth using LAG
  7. Difference between each row and the previous row
  8. First and last order per customer (FIRST_VALUE/LAST_VALUE)
  9. Deduplicate: keep only the most recent row per key
  10. Sessionize: group events separated by 30 minutes or less
  11. Compute 7-day retention after signup
  12. Cumulative percentage of total sales

Worked answer (Q30): the three ranking functions. They look interchangeable until ties appear, and interviewers plant ties to force the choice:

WITH employees(name, salary) AS (
  VALUES ('Dana', 300), ('Eli', 200), ('Fay', 200), ('Gus', 100)
)
SELECT
  name,
  salary,
  ROW_NUMBER() OVER (ORDER BY salary DESC, name) AS row_num,
  RANK()       OVER (ORDER BY salary DESC)       AS rnk,
  DENSE_RANK() OVER (ORDER BY salary DESC)       AS dense_rnk
FROM employees;
namesalaryrow_numrnkdense_rnk
Dana300111
Eli200222
Fay200322
Gus100443

Note the tiebreaker (, name) inside ROW_NUMBER's ordering: without it, Eli and Fay could swap between runs, and a nondeterministic "latest row" is a real production bug, not a style nit.

top 50 sql interview questions with answers 2026

FunctionTies getSequence after a tieUse when
ROW_NUMBER()Distinct numbersContinues 1,2,3,4You need exactly one row per group (dedupe, "latest")
RANK()Same numberSkips (1,2,2,4)Leaderboard where gaps are meaningful
DENSE_RANK()Same numberNo gaps (1,2,2,3)"Nth highest distinct value" regardless of how many rows tie

That last row also answers the classic "second highest salary" question: DENSE_RANK() = 2 handles duplicated top salaries where ROW_NUMBER would not.

Worked answer (Q31): top N per group. Number rows inside each partition, then filter in an outer query. You cannot filter on a window function in WHERE because WHERE runs before windows are computed:

SELECT product_id, category, revenue
FROM (
  SELECT
    product_id,
    category,
    revenue,
    ROW_NUMBER() OVER (
      PARTITION BY category
      ORDER BY revenue DESC
    ) AS rn
  FROM product_revenue
) AS ranked
WHERE rn <= 3;

Asked at MetaFind the most-used app You work with Oculus engagement data: a user_activity table of sessions with durations, joined to an apps lookup. For the trailing 7 days from a given as-of date, you must identify the "most used app" — and defend your definition, since total duration, distinct users, and session count can each crown a different winner. Follow-ups probe how your query behaves under ties and whether your date window is inclusive on both ends.

That Meta question is this exact top-N pattern plus a definitional negotiation, which is the realistic version: in production interviews, half the battle is pinning down what "most used" means before you write a line.

Worked answer (Q32): running total. An ORDER BY inside OVER turns a plain aggregate into a cumulative one:

WITH sales(sale_date, amount) AS (
  VALUES (DATE '2026-01-01', 100),
         (DATE '2026-01-02',  50),
         (DATE '2026-01-03',  75)
)
SELECT
  sale_date,
  amount,
  SUM(amount) OVER (ORDER BY sale_date) AS running_total
FROM sales
ORDER BY sale_date;
sale_dateamountrunning_total
2026-01-01100100
2026-01-0250150
2026-01-0375225

For the cumulative percentage (Q40), divide by the grand total: SUM(amount) OVER (ORDER BY sale_date) / SUM(amount) OVER (). An empty OVER () is a window over the whole result set.

Asked at DoorDashCompute Fitness App DAU Given a users table (with a test-user flag and timezone) and an app_events log, define DAU as distinct non-test users with at least one event per UTC calendar day, and return one row per day for the last 30 days. The traps are the days with zero events — which must still appear as rows — and excluding test users without accidentally dropping legitimate ones. It reads like a one-liner and is actually a calendar-spine problem.

Worked answer (Q33): moving average. Once you have a daily series like that DAU output, the 7-day moving average is a frame clause:

SELECT
  activity_date,
  dau,
  AVG(dau) OVER (
    ORDER BY activity_date
    ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
  ) AS dau_7d_avg
FROM daily_active_users
ORDER BY activity_date;

Say the caveat before the interviewer does: ROWS BETWEEN 6 PRECEDING counts rows, not days. If dates are missing from the table, the "7-day" average silently spans more than 7 calendar days — which is exactly why the DoorDash question forces zero-event days into the series first.

Worked answer (Q34): month-over-month growth. LAG pulls the previous row's value into the current row:

SELECT
  month,
  revenue,
  revenue - LAG(revenue) OVER (ORDER BY month) AS mom_change,
  ROUND(
    100.0 * (revenue - LAG(revenue) OVER (ORDER BY month))
    / NULLIF(LAG(revenue) OVER (ORDER BY month), 0)
  , 1) AS mom_pct
FROM monthly_revenue
ORDER BY month;

The NULLIF(..., 0) guard matters: a zero-revenue month would otherwise crash the query with a division error. Interviewers notice when you add it unprompted.

Worked answer (Q36): the LAST_VALUE trap. FIRST_VALUE works as expected, but LAST_VALUE with a default frame returns the last peer of the current row — the current row itself whenever the ORDER BY values are unique — because the default RANGE frame ends at CURRENT ROW. You must widen it explicitly:

SELECT DISTINCT
  customer_id,
  FIRST_VALUE(order_id) OVER w AS first_order,
  LAST_VALUE(order_id)  OVER (
    PARTITION BY customer_id ORDER BY order_date
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  ) AS last_order
FROM orders
WINDOW w AS (PARTITION BY customer_id ORDER BY order_date);

Knowing this trap cold is one of the fastest ways to signal real window-function experience, because everyone who has used LAST_VALUE in production has been burned by it once.

Worked answer (Q38): sessionization. Flag rows that start a new session, then running-sum the flags into session ids:

WITH flagged AS (
  SELECT
    user_id,
    event_ts,
    CASE
      WHEN event_ts - LAG(event_ts) OVER (
             PARTITION BY user_id ORDER BY event_ts
           ) <= INTERVAL '30 minutes'
      THEN 0
      ELSE 1
    END AS new_session
  FROM events
)
SELECT
  user_id,
  event_ts,
  SUM(new_session) OVER (
    PARTITION BY user_id ORDER BY event_ts
  ) AS session_id
FROM flagged;

The ELSE 1 branch also catches each user's first event, where LAG returns NULL and the comparison is unknown. That is a rare case where SQL's three-valued logic works in your favor — but only if you put the "same session" test in the WHEN and let everything else fall through.

Questions 41–50: CTEs, DML, and optimization

Data engineering screens hit this section hard; analyst and data science screens sample from it. Expect at least one conceptual "explain the difference" question here alongside the coding.

  1. Rewrite a nest of subqueries as chained CTEs
  2. Recursive CTE: build an employee hierarchy from manager_id
  3. Longest streak of consecutive login days (gaps and islands)
  4. Build a funnel: signup, activation, first purchase
  5. UPDATE a table from another table; write an UPSERT
  6. Delete duplicate rows while keeping one copy
  7. Explain DELETE vs TRUNCATE vs DROP
  8. When does an index help, and when does it hurt?
  9. Debug and optimize a slow or wrong query someone else wrote
  10. Explain the logical execution order of a query

Worked answer (Q37/Q46 pattern): dedupe, keep the latest. This is the single most reused CTE-plus-window pattern in interviews, and it answers both the read version (Q37) and the delete version (Q46):

WITH ranked AS (
  SELECT
    *,
    ROW_NUMBER() OVER (
      PARTITION BY email
      ORDER BY created_at DESC
    ) AS rn
  FROM users
)
SELECT *
FROM ranked
WHERE rn = 1;

For the destructive version, delete where rn > 1 using the table's primary key. Mention that you would run the SELECT first and check counts — interviewers score the safety habit.

Worked answer (Q42): recursive hierarchy. An anchor selects the roots; the recursive member repeatedly joins children onto what has been found so far:

WITH RECURSIVE org AS (
  -- anchor: top-level managers
  SELECT employee_id, manager_id, name, 1 AS depth
  FROM employees
  WHERE manager_id IS NULL

  UNION ALL

  -- recursive member: everyone reporting up the chain
  SELECT e.employee_id, e.manager_id, e.name, org.depth + 1
  FROM employees AS e
  JOIN org ON e.manager_id = org.employee_id
)
SELECT employee_id, name, depth
FROM org
ORDER BY depth, employee_id;

Worked answer (Q43): gaps and islands. Consecutive dates minus a consecutive row number is constant within a streak. That constant becomes the group key:

WITH distinct_days AS (
  SELECT DISTINCT user_id, login_date
  FROM logins
),
grouped AS (
  SELECT
    user_id,
    login_date,
    login_date - (ROW_NUMBER() OVER (
      PARTITION BY user_id ORDER BY login_date
    ))::int AS streak_key
  FROM distinct_days
)
SELECT
  user_id,
  COUNT(*)        AS streak_length,
  MIN(login_date) AS streak_start
FROM grouped
GROUP BY user_id, streak_key
ORDER BY streak_length DESC;

The DISTINCT step is not optional. Two logins on the same day would otherwise increment the row number without advancing the date and split a real streak in two.

Asked at OpenAIWrite SQL for repeat churn You are given an experiment_users table assigning users to a control or free-month variant, plus a subscription_events log, and asked to measure how the promotion changed retention and churn. Eligibility is already handled upstream — part of the test is whether you resist re-deriving it. Ordering each user's event history with window functions and comparing outcomes per variant does most of the work.

That question is the production shape of Q39 and Q44 combined: cohort the users, walk each one's event sequence in order, then aggregate per group. If you can chain those three steps as named CTEs, you can solve most experiment-analysis SQL on sight.

Worked answer (Q45): UPDATE from another table, and UPSERT. PostgreSQL's UPDATE ... FROM joins during the update; INSERT ... ON CONFLICT handles the upsert case:

UPDATE employees AS e
SET salary = a.new_salary
FROM salary_adjustments AS a
WHERE a.employee_id = e.employee_id;

INSERT INTO daily_metrics (metric_date, dau)
VALUES (DATE '2026-01-03', 4210)
ON CONFLICT (metric_date)
DO UPDATE SET dau = EXCLUDED.dau;

Worth saying in the room: MERGE is the standard spelling (and exists in PostgreSQL 15+), but every warehouse dialect has its own upsert idiom, so name the engine before writing one.

Worked answer (Q47): DELETE vs TRUNCATE vs DROP. Conceptual, but precise wording matters:

CommandRemovesKeeps table structure?Logged per row?Can filter with WHERE?
DELETESelected rowsYesYes (slower, rollback-friendly)Yes
TRUNCATEAll rowsYesMinimal (fast; resets sequences in MySQL/SQL Server, in Postgres only with RESTART IDENTITY)No
DROPThe whole tableNon/aNo

Asked at TikTokDebug a Hive query The interviewer hands you a prewritten Hive query that returns wrong numbers and asks you to find the logic errors: join conditions, filters, aggregation grain, window definitions, partition predicates. Then you fix it, design test cases to prove the fix, and propose performance improvements. Nothing here is exotic — the skill being tested is a systematic read of someone else's SQL.

Asked at TikTokDebug a Hive insert query The write-path variant: given a target table schema, an incoming table, and an INSERT/SELECT that fails, explain why. The checklist runs through column alignment, type casts, partition columns and dynamic-partition settings, file formats, and reserved keywords. Data engineering screens use this to check that you know how tables are written, not just read.

Both TikTok questions reward the same habit: verify the grain and row count at every stage instead of staring at the final output. When debugging under time pressure, comment out everything after the first join, check the count, and work outward.

Worked answer (Q50): logical execution order. SQL evaluates clauses in a fixed order that is not the order you write them: FROM (and joins), then WHERE, GROUP BY, HAVING, window functions, SELECT, DISTINCT, ORDER BY, LIMIT.

top 50 sql interview questions with answers 2026

This one fact resolves a whole cluster of interview gotchas: you cannot reference a SELECT alias in WHERE (it does not exist yet), you can in ORDER BY (it does), and you cannot filter on a window function anywhere except an outer query, because windows are computed after HAVING.

Common mistakes that fail SQL screens

DoDon't
State your assumptions about the schema out loudSilently assume column names and join keys
Use LEFT JOIN ... IS NULL or NOT EXISTS for "who is missing"Reach for NOT IN with a nullable subquery (returns nothing if any value is NULL)
Name CTEs after what they produceNest five subqueries no one can read
Guard division with NULLIF(denominator, 0)Let a zero denominator crash the query
Verify row counts after each joinAssume the join is one-to-one and ship a fan-out
Pick ROW_NUMBER/RANK/DENSE_RANK on purposeDefault to RANK and get surprised by gaps
Add a tiebreaker to every ORDER BY inside ROW_NUMBERShip a nondeterministic "latest row"

Practice these on PracHub

Reading answers builds recognition; writing them under pressure builds recall. These questions from the PracHub question bank map directly onto the sections above, and many run in an in-browser SQL editor against real PostgreSQL, so you get the same "wrong row count" feedback you would get in the screen itself.

The bank goes well beyond these six: it holds SQL questions from interviews at Google, Meta, and Amazon, filterable by company and difficulty. If you are targeting an analytics-heavy role, pair this list with the Data Scientist question set and the broader interview guides for the case and behavioral rounds.

If you want structured drilling beyond the bank, we have compared the main platforms head-to-head in LeetCode SQL vs DataLemur and reviewed StrataScratch's free tier.

How to turn this list into a prep plan

Do not treat this as passive reading. Convert it into a loop: learn one pattern, close the page, re-derive the worked answer on a blank editor, then solve one bank question that uses it.

Prep areaWhat you need to provePractice artifact
Pattern matchingName the family before you typeOne sentence: "this is a top-N-per-group problem"
Join correctnessRight join type, right keys, no fan-outA query with a row-count check after each join
Window fluencyPartition, order, and frame chosen on purposeA ROW_NUMBER dedupe and a LAG comparison from memory
NULL disciplineUnknowns handled explicitlyNULLIF guards and IS DISTINCT FROM used without prompting
CommunicationTurn a result into a recommendationOne concise business interpretation of the output

A week per section is a comfortable pace from a cold start; candidates who already write SQL daily usually need only the window-function and gaps-and-islands sections plus timed practice. Either way, finish with two full mock screens where you talk while you type. The talking is the part nobody practices.

FAQ

What are the most common SQL interview questions?

Joins with a NULL check ("customers who never ordered"), top-N-per-group with ROW_NUMBER, running totals and moving averages, month-over-month comparisons with LAG, dedup keeping the latest row, and WHERE vs HAVING. Joins and window functions appear in nearly every screen, so prioritize sections two and four of this list.

What SQL interview questions do freshers get?

Freshers get the first three families on this list, roughly Q1–28: filtering and NULL logic, joins and set operations, and aggregation with GROUP BY and CASE. Typical prompts are "find customers who never ordered", "total spend per user", "departments with more than 10 staff", and "explain WHERE vs HAVING". Window functions turn up occasionally as a stretch question, but entry-level screens weight correct NULL handling and clear reasoning far more than advanced syntax.

How long does it take to prepare for a SQL interview?

From a cold start, most people get screen-ready in three to five weeks by drilling one section of this list per week: basics, joins, aggregation, window functions, then CTEs and DML. If you already write SQL at work, one to two weeks focused on window functions and timed practice is usually enough. Practicing on a live SQL editor that reports row counts beats reading answers by a wide margin.

What is the difference between WHERE and HAVING?

WHERE filters individual rows before grouping; HAVING filters groups after aggregation. Use WHERE to drop rows you never want counted and HAVING for conditions on aggregates like COUNT(*) > 10. Because WHERE runs first, it is also the cheaper place to filter whenever the condition does not need an aggregate.

What SQL interview questions do experienced candidates get?

Experienced screens start where the basics end, around Q29–50: window functions (ranking with ties, LAG comparisons, running totals, frame clauses), gaps-and-islands problems such as the longest login streak, sessionization, cohort and retention analysis built from chained CTEs, and debugging or optimizing a query someone else wrote. Follow-ups ask why you chose an approach and how it behaves at scale, which counts for more than getting the first draft right.

Do data science interviews test SQL or Python?

Usually both, in separate rounds: SQL for the analytics screen, Python for the coding round, and either one for take-homes. The patterns transfer — a GROUP BY is a pandas groupby, a window function is a transform — so drilling one sharpens the other. When you get to choose the tool, Python vs SQL in Data Science Interviews breaks down which to pick per question type.


Comments (0)