SQL is the one skill that shows up in almost every data analyst, backend, and data engineer interview - and it is the one candidates most often underprepare for. This guide walks through how SQL interviews actually run, which questions come up by role, and gives you concise, runnable answers to the questions you are most likely to face.
Whether you are a self-taught developer, a bootcamp graduate, or a working engineer switching teams, the pattern is the same: interviewers do not want trivia, they want to see that you can reason about data. Below you will find a study plan, a map of what gets asked, and dozens of worked answers you can practice against. If you want to build the underlying skills first, start with the SQL beginner course and then move on to the advanced SQL course.
What this guide covers
- How SQL interviews work & how to prepare
- Question categories by role & seniority
- Fundamentals Q&A
- Joins Q&A
- Aggregation Q&A
- Subqueries & CTEs Q&A
- Window functions Q&A
- Classic SQL puzzles
- Indexes & performance Q&A
- Design & normalization Q&A
- Behavioral & approach tips
- Mistakes to avoid & final checklist
1. How SQL interviews work & how to prepare
SQL interviews are rarely a single event. Most companies run a funnel of two to four stages, and each stage tests a slightly different thing. Knowing the format up front lets you rehearse the right way instead of memorizing answers that never come up.
| Stage | What it looks like | What they are really testing |
|---|---|---|
| Online screen | Automated platform (HackerRank, CoderPad) with 3 - 6 timed SQL questions | Can you write correct SQL under time pressure without a reference open |
| Take-home | A dataset plus 5 - 10 business questions to answer in SQL and prose | Query correctness, readability, and whether you explain your findings |
| Live coding | Shared editor with an interviewer watching you type and talk | Your thought process, debugging, and how you handle hints |
| Whiteboard / verbal | No editor; you write or describe a query and reason about it aloud | Whether you understand the concepts, not just the syntax |
The most common trap is preparing only for the screen (memorize syntax) and freezing in the live round (talk it through). Practice both modes. For the live round in particular, get comfortable narrating: state your assumptions, name the tables and keys, then write the query.
A word on dialects. Interview datasets are usually MySQL or PostgreSQL, occasionally SQL Server. Ninety percent of what gets asked is standard SQL that runs anywhere; the differences are at the edges - string functions, date arithmetic, LIMIT vs TOP, and whether recursive CTEs need the RECURSIVE keyword. You do not need to memorize every dialect. You need to write clean standard SQL and flag the one or two spots where syntax varies. That signals maturity far more than reciting vendor-specific trivia.
A four-week study plan
Week 1 - Fundamentals
Drill SELECT, WHERE, GROUP BY, HAVING, ORDER BY, and every join type until they are automatic. Rebuild the SQL cheat sheet from memory.
Week 2 - Joins & aggregation
Work through anti-joins, self-joins, and conditional aggregation with CASE. Study SQL joins until you can draw each one.
Week 3 - Windows & puzzles
Master ROW_NUMBER, RANK, DENSE_RANK, and running totals. Solve the classic puzzles below (Nth highest, duplicates, gaps-and-islands) three times each.
Week 4 - Performance & mock rounds
Learn indexes and EXPLAIN, then do timed mock interviews out loud. Review your advanced SQL functions so nothing is rusty.
Tip: Interviewers usually accept any correct standard-SQL answer. When a function differs across databases (for example LIMIT in Postgres/MySQL vs TOP in SQL Server), say which dialect you are writing and offer the portable alternative.
2. Question categories by role & seniority
Not every role weights SQL topics the same way. A data analyst lives in aggregation and window functions; a backend engineer needs joins, transactions, and indexing; a data engineer gets the hardest performance and modeling questions. The table below maps what to expect.
| Topic | Data Analyst | Backend Engineer | Data Engineer |
|---|---|---|---|
| Filtering & aggregation | Core | Common | Common |
| Joins & anti-joins | Core | Core | Core |
| Window functions | Core | Sometimes | Core |
| Subqueries & CTEs | Common | Common | Core |
| Indexing & EXPLAIN | Light | Core | Core |
| Schema design & normalization | Light | Core | Core |
| Transactions & concurrency | Rare | Core | Common |
Seniority changes the depth, not the topics. A junior candidate is asked to write a correct query; a senior candidate is asked why the query is slow, how it behaves on 500 million rows, and what they would change in the schema. Across hundreds of reported interview loops, these are the topics that come up most often:
3. Fundamentals Q&A
These are warm-up questions. Get them right instantly and confidently - hesitation here signals shaky foundations. They also double as concept checks in verbal rounds, so practice saying the answer in one or two crisp sentences before you write any SQL.
What is the difference between WHERE and HAVING?
WHERE filters individual rows before grouping and aggregation. HAVING filters after aggregation, so it can reference aggregate functions like COUNT or SUM that WHERE cannot see.
-- WHERE keeps only 2024 orders, THEN groups,
-- THEN HAVING keeps only big-spending customers.
SELECT customer_id, SUM(amount) AS total
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY customer_id
HAVING SUM(amount) > 1000;DELETE vs TRUNCATE vs DROP?
All three remove data, but at different levels and with different consequences.
| Command | Removes | Can filter with WHERE? | Logged / rollback | Resets identity |
|---|---|---|---|---|
DELETE | Rows | Yes | Row-by-row, fully rollback-able | No |
TRUNCATE | All rows | No | Minimal logging, fast | Usually yes |
DROP | The whole table (structure too) | No | Removes the object entirely | N/A |
DELETE FROM logs WHERE created_at < '2023-01-01'; -- selective
TRUNCATE TABLE staging_import; -- empty it fast
DROP TABLE temp_scratch; -- gone completelyPrimary key vs unique key?
A primary key uniquely identifies each row: it must be unique and not null, and a table has exactly one. A unique key also enforces uniqueness, but it allows one null (in most databases) and you can have several per table. The primary key is typically the row's identity; unique keys enforce business rules like "one email per user."
CREATE TABLE users (
id INT PRIMARY KEY, -- one identity, never null
email VARCHAR(255) UNIQUE, -- business rule, allows one null
phone VARCHAR(20) UNIQUE
);What is a NULL?
NULL means "unknown" or "missing," not zero and not an empty string. The key interview point: NULL is never equal to anything, including another NULL. You must test it with IS NULL / IS NOT NULL, and any arithmetic or comparison involving NULL yields NULL (which behaves as false in a WHERE clause).
-- Wrong: never matches, because NULL = NULL is NULL, not true
SELECT * FROM t WHERE deleted_at = NULL;
-- Right
SELECT * FROM t WHERE deleted_at IS NULL;
-- Substitute a default with COALESCE
SELECT COALESCE(nickname, name, 'Anonymous') FROM users;What does DISTINCT do?
DISTINCT removes duplicate rows from the result set, considering all selected columns together. A common gotcha: SELECT DISTINCT a, b deduplicates on the pair (a, b), not on a alone.
-- Distinct countries that placed an order
SELECT DISTINCT country FROM orders;
-- Count of unique customers (dedupe inside the aggregate)
SELECT COUNT(DISTINCT customer_id) FROM orders;UNION vs UNION ALL?
Both stack the rows of two result sets that have matching columns. UNION removes duplicate rows, which forces a sort or hash to detect them and is therefore slower. UNION ALL keeps every row, including duplicates, and is faster. Reach for UNION ALL unless you specifically need deduplication - a surprising number of candidates default to UNION and pay for a sort they never needed.
-- Combine current and archived orders, keep everything
SELECT id, amount FROM orders
UNION ALL
SELECT id, amount FROM orders_archive;4. Joins Q&A
Joins are the single most-tested topic. Expect at least one join question in every loop. If you need a refresher, the joins guide walks through each type visually.
INNER JOIN vs LEFT JOIN?
An INNER JOIN returns only rows that have a match in both tables. A LEFT JOIN returns every row from the left table, filling columns from the right table with NULL where there is no match. Use LEFT JOIN whenever "keep everyone even if they have no related record" matters.
-- Every customer, with their order count (0 if none)
SELECT c.name, COUNT(o.id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.name;Find rows with no match (anti-join)
A classic: "which customers have never placed an order?" The cleanest answer is a LEFT JOIN where the right side is NULL, or NOT EXISTS.
-- Approach A: LEFT JOIN ... IS NULL
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;
-- Approach B: NOT EXISTS (often better with NULLs)
SELECT c.id, c.name
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.id
);Watch out: Prefer NOT EXISTS over NOT IN for anti-joins. If the subquery inside NOT IN returns even a single NULL, the whole condition becomes unknown and you get zero rows back.
Self-join: find each employee's manager
A self-join joins a table to itself using different aliases. The textbook example is an employees table where manager_id points at another row's id.
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id
ORDER BY manager;Using LEFT JOIN here keeps the CEO (whose manager_id is NULL) in the result with a NULL manager.
Why did my SUM double after adding a join?
This is a subtle question that trips up strong candidates. When you join a table to another on a one-to-many relationship, each parent row is repeated once per child row - so any aggregate over the parent's own columns is inflated. The fix is to aggregate the many-side before joining, usually in a subquery or CTE, so the join operates on already-summarized rows.
-- customer.credit is counted once per order row
SELECT c.id, SUM(c.credit), SUM(o.amount)
FROM customers c
JOIN orders o ON o.customer_id = c.id
GROUP BY c.id;-- Pre-aggregate orders, then join one row per customer
SELECT c.id, c.credit, o.total
FROM customers c
JOIN (
SELECT customer_id, SUM(amount) AS total
FROM orders GROUP BY customer_id
) o ON o.customer_id = c.id;5. Aggregation Q&A
What is the GROUP BY rule?
Every column in the SELECT list must either appear in GROUP BY or be wrapped in an aggregate function. In strict databases (Postgres, SQL Server, MySQL with ONLY_FULL_GROUP_BY), violating this is an error; older MySQL silently picked an arbitrary value, which is a bug waiting to happen.
-- department is neither grouped nor aggregated
SELECT department, name, AVG(salary)
FROM employees
GROUP BY department;SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department;Count rows per group
SELECT status, COUNT(*) AS n
FROM orders
GROUP BY status
ORDER BY n DESC;Remember the difference: COUNT(*) counts rows, COUNT(col) counts non-null values of that column, and COUNT(DISTINCT col) counts unique non-null values. This distinction is a common gotcha - if an interviewer asks "how many customers placed an order," they usually mean COUNT(DISTINCT customer_id), not COUNT(*), because one customer can have many orders.
Watch out for GROUP BY dropping empty groups
A frequent follow-up: "show every status, even ones with zero orders." A plain GROUP BY only returns statuses that actually appear in the data, so categories with no rows vanish. To include them you must start from the complete list of categories and LEFT JOIN the counts onto it.
-- Every status listed, zero-filled where there are no orders
SELECT s.status, COUNT(o.id) AS n
FROM statuses s
LEFT JOIN orders o ON o.status = s.status
GROUP BY s.status
ORDER BY n DESC;Conditional aggregation with CASE
This is a favorite because it separates people who memorize syntax from people who understand it. Pivoting counts by category in a single pass uses SUM or COUNT around a CASE.
-- One row per month with paid/refunded counts side by side
SELECT
DATE_TRUNC('month', order_date) AS month,
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid,
SUM(CASE WHEN status = 'refunded' THEN 1 ELSE 0 END) AS refunded
FROM orders
GROUP BY DATE_TRUNC('month', order_date);6. Subqueries & CTEs Q&A
What is a correlated subquery?
A correlated subquery references a column from the outer query, so it is re-evaluated once per outer row. It is powerful but can be slow. The example below finds employees who earn more than their department's average.
SELECT e.name, e.department, e.salary
FROM employees e
WHERE e.salary > (
SELECT AVG(x.salary)
FROM employees x
WHERE x.department = e.department -- correlation
);What is a CTE (WITH clause)?
A Common Table Expression is a named, temporary result set defined with WITH that exists only for the duration of the query. CTEs make complex queries readable by naming each step, and they can be referenced multiple times.
WITH dept_avg AS (
SELECT department, AVG(salary) AS avg_sal
FROM employees
GROUP BY department
)
SELECT e.name, e.salary, d.avg_sal
FROM employees e
JOIN dept_avg d ON d.department = e.department
WHERE e.salary > d.avg_sal;Recursive CTE
A recursive CTE references itself, which lets you walk hierarchies (org charts, category trees, bill-of-materials) or generate sequences. It has an anchor member, a UNION ALL, and a recursive member that stops when it returns no rows.
-- Everyone in the reporting chain under manager 1
WITH RECURSIVE chain AS (
SELECT id, name, manager_id, 1 AS depth
FROM employees WHERE id = 1 -- anchor
UNION ALL
SELECT e.id, e.name, e.manager_id, c.depth + 1
FROM employees e
JOIN chain c ON e.manager_id = c.id -- recursive step
)
SELECT * FROM chain ORDER BY depth;7. Window functions Q&A
Window functions compute across a set of rows related to the current row without collapsing them into a group. They are the single biggest differentiator between junior and mid-level candidates. Study the window functions guide and advanced SQL functions if this section feels new.
ROW_NUMBER vs RANK vs DENSE_RANK
Interviewers love to probe whether you actually understand the three ranking functions, because they behave differently only when there are ties - and ties are exactly where bugs hide.
| Function | Behavior on ties | Example sequence |
|---|---|---|
ROW_NUMBER | Always unique; breaks ties arbitrarily | 1, 2, 3, 4 |
RANK | Ties share a rank, then skips | 1, 2, 2, 4 |
DENSE_RANK | Ties share a rank, no gaps | 1, 2, 2, 3 |
Rule of thumb: use ROW_NUMBER to pick exactly one row per group (deduping, top-1), DENSE_RANK for "the Nth distinct value," and RANK when you want competition-style rankings where ties leave a gap.
Find the 2nd (Nth) highest salary - three ways
This is the most-asked SQL interview question in existence. Show more than one method; it demonstrates range.
-- Way 1: LIMIT / OFFSET (Postgres, MySQL)
SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1; -- skip #1, take the next-- Way 2: correlated subquery (portable, no window functions)
SELECT MAX(salary)
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);-- Way 3: DENSE_RANK (generalizes to Nth, handles ties)
WITH ranked AS (
SELECT salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employees
)
SELECT DISTINCT salary FROM ranked WHERE rnk = 2;Use DENSE_RANK (not ROW_NUMBER) when ties should share a rank - two people tied for first both count as rank 1, so the "second highest" is genuinely the second distinct salary.
Top-N per group
"The top 3 earners in each department" is the group version. Partition, rank, then filter.
WITH ranked AS (
SELECT name, department, salary,
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary DESC) AS rn
FROM employees
)
SELECT name, department, salary
FROM ranked
WHERE rn <= 3;Running total
SELECT order_date, amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM orders
ORDER BY order_date;8. Classic SQL puzzles
These pattern-based problems recur across companies. Learn the shape of each solution and you can adapt it on the spot. The goal is not to memorize the exact query but to recognize the family a problem belongs to: "this is a top-N-per-group," "this is a gaps-and-islands," "this is a self-join." Once you name the pattern, the query almost writes itself. Practice each one until you can produce it in under three minutes on a blank editor.
Find duplicates
-- Emails used by more than one row
SELECT email, COUNT(*) AS n
FROM users
GROUP BY email
HAVING COUNT(*) > 1;Delete duplicates, keeping one
Number the copies within each duplicate group, then delete everything past the first.
WITH dups AS (
SELECT id,
ROW_NUMBER() OVER (
PARTITION BY email ORDER BY id) AS rn
FROM users
)
DELETE FROM users
WHERE id IN (SELECT id FROM dups WHERE rn > 1);Consecutive streaks (gaps-and-islands)
Find users who logged in on 3 or more consecutive days. The trick: subtract a ROW_NUMBER from the date - consecutive dates produce a constant difference, which becomes a group key.
WITH marked AS (
SELECT user_id, login_date,
login_date - (ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY login_date) * INTERVAL '1 day') AS grp
FROM logins
)
SELECT user_id, MIN(login_date) AS streak_start,
COUNT(*) AS streak_len
FROM marked
GROUP BY user_id, grp
HAVING COUNT(*) >= 3;Pivot rows to columns
-- Sales matrix: one row per product, one column per quarter
SELECT product,
SUM(CASE WHEN quarter = 'Q1' THEN amount END) AS q1,
SUM(CASE WHEN quarter = 'Q2' THEN amount END) AS q2,
SUM(CASE WHEN quarter = 'Q3' THEN amount END) AS q3,
SUM(CASE WHEN quarter = 'Q4' THEN amount END) AS q4
FROM sales
GROUP BY product;Compute the median
Many databases lack a MEDIAN aggregate. The portable approach ranks values from both ends and averages the middle one or two.
WITH ranked AS (
SELECT salary,
ROW_NUMBER() OVER (ORDER BY salary) AS asc_r,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS desc_r,
COUNT(*) OVER () AS n
FROM employees
)
SELECT AVG(salary) AS median
FROM ranked
WHERE asc_r IN (desc_r, desc_r + 1, desc_r - 1);On Postgres you can shortcut this with PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) - mention the built-in but be ready to derive it by hand.
9. Indexes & performance Q&A
For backend and data engineering roles this section is where senior candidates are separated from the rest. Read the indexes guide for the full mechanics.
What is an index and how does it help?
An index is a separate, sorted data structure (usually a B-tree) that lets the database find rows without scanning the whole table - think of it as the index at the back of a book. It turns an O(n) scan into an O(log n) lookup for equality and range queries, and it can satisfy ORDER BY without a sort. The cost is slower writes and extra storage, because every insert and update must also maintain the index.
CREATE INDEX idx_orders_customer
ON orders (customer_id);
-- Composite index: order matters, leftmost prefix wins
CREATE INDEX idx_orders_cust_date
ON orders (customer_id, order_date);Why might a query be slow?
- No index on the columns in
WHEREorJOIN, forcing a full table scan. - A function on an indexed column (for example
WHERE YEAR(order_date) = 2024) makes the index unusable - the index is onorder_date, not onYEAR(order_date). SELECT *pulling far more data than needed, or missing a covering index.- Stale statistics leading the planner to a bad join order, or a join that explodes row counts.
-- Wraps the column, index cannot be used
SELECT * FROM orders WHERE YEAR(order_date) = 2024;-- Sargable range keeps the index in play
SELECT * FROM orders
WHERE order_date >= '2024-01-01'
AND order_date < '2025-01-01';How do you use EXPLAIN?
EXPLAIN (or EXPLAIN ANALYZE, which actually runs the query and reports real timings) shows the execution plan. You are looking for sequential scans on big tables, high estimated row counts, expensive sorts, and the join strategy chosen.
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 42;
-- Look for "Index Scan" (good) vs "Seq Scan" (often bad on large tables)Do not just read the plan - interpret it. Compare the planner's estimated rows against the actual rows: a large gap means statistics are stale and the optimizer is guessing. Watch for a nested loop over millions of rows (usually wants a hash join), an external sort spilling to disk, and any scan whose cost dominates the total. The senior move is to name a specific change and predict its effect: "adding an index on customer_id turns this Seq Scan into an Index Scan and drops it from 400ms to under 5ms."
10. Design & normalization Q&A
Explain 1NF, 2NF, and 3NF in one line each
Normalization removes redundancy so data is stored once and updated in one place. Learn it properly in the database normalization guide.
| Form | One-line rule |
|---|---|
| 1NF | Atomic values only - no repeating groups or comma-lists in a single column. |
| 2NF | 1NF plus every non-key column depends on the whole primary key, not part of it. |
| 3NF | 2NF plus no non-key column depends on another non-key column (no transitive dependencies). |
When would you denormalize?
Deliberately, when read performance matters more than write simplicity: reporting tables, dashboards, and read-heavy APIs. You trade duplicated data and heavier writes for fewer joins at read time. The honest interview answer is "normalize first for correctness, then denormalize specific hot paths with evidence from EXPLAIN, not by guessing." Techniques include summary/rollup tables, cached counts, and materialized views.
What is a foreign key and why use one?
A foreign key is a column that references the primary key of another table, and the database enforces that every value either matches an existing parent row or is null. This is referential integrity: you cannot insert an order for a customer that does not exist, and depending on the rule you set (ON DELETE CASCADE, RESTRICT, or SET NULL) the database controls what happens to children when a parent is deleted. Interviewers like this question because it reveals whether you think about data integrity at the schema level or only in application code.
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT NOT NULL,
FOREIGN KEY (customer_id)
REFERENCES customers(id)
ON DELETE CASCADE
);Strong answer: Frame denormalization as a measured tradeoff. Saying "I would add a materialized daily-sales table because the dashboard query joins five tables and runs every 30 seconds" shows judgment, not just knowledge.
11. Behavioral & approach tips
In live rounds, how you think is graded as heavily as the final query. A silent candidate who writes a perfect answer often scores lower than one who narrates a slightly rougher one, because the interviewer cannot follow perfect silence.
Restate and clarify
Repeat the question in your own words and confirm the schema: table names, keys, and whether nulls or duplicates are possible. Ask about expected data volume.
State your plan before typing
Say "I will join orders to customers, group by customer, then filter with HAVING." A one-sentence plan lets the interviewer redirect you early.
Write, then test aloud
Walk a tiny sample through the query. Call out edge cases: empty groups, ties, nulls, and off-by-one on OFFSET.
Optimize only if asked
Get a correct answer first, then offer "if this table were huge I would index customer_id and check the plan with EXPLAIN."
When you get stuck, think out loud rather than going quiet: "I need the second highest, so I could rank rows and take rank 2, or take the max below the overall max." Interviewers give hints to candidates who are visibly reasoning. If you have mentorship gaps, structured practice through SQL mentorship gives you feedback you cannot get from solving problems alone.
For take-home assignments the grading criteria shift. Correctness is assumed; what earns marks is communication. Format your SQL consistently, alias tables meaningfully, add a short comment above each non-obvious query, and write one or two sentences interpreting each result - "revenue is concentrated in the top 5 percent of customers, which suggests the pricing tier is working." Reviewers are reading dozens of submissions; the one that reads like a colleague's pull request stands out. Never over-engineer a take-home with clever tricks that are hard to follow; clear beats clever every time.
On the behavioral side, be ready to tell a story about a real query you optimized or a data problem you debugged. Use the situation-action-result shape: what was slow or wrong, what you changed, and the measurable outcome. Concrete numbers ("cut the nightly report from 40 minutes to 3") land far harder than "I made it faster."
12. Mistakes to avoid & final checklist
The classic killers: comparing to NULL with = instead of IS NULL; putting an aggregate in WHERE instead of HAVING; forgetting that COUNT(column) skips nulls; using ROW_NUMBER when ties mean you needed DENSE_RANK; and assuming NOT IN is safe when the subquery can return nulls.
A few more that cost offers: reaching for SELECT * in production answers, joining without checking whether the relationship is one-to-many (which silently multiplies your sums), and optimizing before the query is even correct. Slow down and get the logic right first.
There is also a category of soft mistakes that sink otherwise-strong candidates: going silent for two minutes, arguing with a hint instead of trying it, and refusing to admit uncertainty. Interviewers are not looking for someone who knows everything - they are looking for someone they would want to debug a production incident with at midnight. Saying "I am not certain window frames default to RANGE, let me reason it through" reads as honest and senior. Bluffing a confident wrong answer reads as risky. When in doubt, state your assumption clearly and move forward; you can always revise it once you see a result.
Final pre-interview checklist
- I can write all join types from memory, including anti-joins and self-joins.
- I know
WHEREvsHAVINGand theGROUP BYrule cold. - I can solve Nth-highest-salary three different ways.
- I can write top-N-per-group and a running total with window functions.
- I can find and delete duplicates, and handle gaps-and-islands.
- I can read an
EXPLAINplan and explain why a query is slow. - I can state 1NF - 3NF in a sentence and justify denormalization.
- I practice out loud, narrating my plan before I type.
Work through every example on this page until you can reproduce it without looking, then pressure-test yourself with the SQL examples library. Reference the cheat sheet the morning of the interview, keep the window functions guide handy, and you will walk in ready.