⏱ 16 min readTopics chapter readerLevel · Advanced
01 · Overview
How a SQL round works
⏱ 5 min · Topic 1 of 13
A SQL interview round is a short test. You get a question and a few tables. The person who watches you is the interviewer.
The interviewer checks two things. Is your answer right? Can you explain how you got it?
This chapter teaches a method. It does not teach new SQL. It uses what you learned in chapters 1 to 5.
The method has five steps: clarify, contract, build, check, explain. The next section shows each one.
The examples use the same shop as chapters 1 to 3: customers, orders and order_items. Question 1, “Who are our top 3 customers?”, runs through the chapter. Question 2, “What is each customer’s latest order?”, appears once, in “Compare two queries”.
02 · Method
Five steps for every question
⏱ 5 min · Topic 2 of 13
Use the same five steps for every question. Say each step out loud when you do it. Then the interviewer can follow your thinking.
This table shows what you do and what you might say in each step:
The five steps
Step
What you do
What you might say
1. Clarify
Restate the question. Ask about grain, NULL, ties and dates.
“Does one row mean one order or one customer?”
2. Contract
Write down the columns, rows and order of the result.
“I will return one row per customer.”
3. Build
Write the query in small steps. Run each step.
“First I keep the paid orders.”
4. Check
Compare the result with the data.
“The parts add up to the total.”
5. Explain
Say what the query does and what you assumed.
“I counted paid orders only.”
A tie is when two rows have the same value, such as two customers with the same total.
A silent pause looks like no thinking. A spoken step shows your thinking, even when the query is not finished.
The next sections follow Question 1, “Who are our top 3 customers?”, through all five steps.
03 · Clarify
Say the question again
⏱ 6 min · Topic 3 of 13
The question is short on purpose. “Top 3 customers” hides several choices. Say the question in your own words, and name each choice.
Some words are unclear in almost every question. This table shows five of them, and what each one changes in SQL:
Unclear words
Word
Question to ask
What it changes
top
By revenue or by number of orders? How many?
The ORDER BY column and the LIMIT
customer
Anyone who ordered, or every row in customers?
The first table and the join type
revenue
Do refunded or pending orders count?
The WHERE test on status
recent
Which dates? Which date column?
The WHERE test on order_date
active
Active by which action?
The table and the filter
See why this matters. This query counts the paid orders and adds up the paid amounts for each customer:
By number of orders, Asha is first. By revenue, Dev is first.
The question did not say which. A guess may be wrong.
What to say
“Top could mean most orders or most revenue. I will use paid revenue. Tell me if you meant order count.”
04 · Clarify
Questions to ask first
⏱ 6 min · Topic 4 of 13
Ask your questions before you write any SQL. A late answer can mean you start again.
Here are six questions to ask before you write SQL:
Grain: what does one row of the answer mean? One row per customer, or per order?
Rows: which rows count? Only paid orders? Do refunded orders count?
NULL: what should happen to a missing value?
Ties: what if two customers have the same total?
Dates: which date column? Which start and end date?
Output: which columns, in which order, and how many rows?
For Question 1, the talk might go like this:
You: “Does top mean most revenue? Do refunded orders count? What if two customers tie?”
Interviewer: “Most revenue. Paid orders only. Break ties by name.”
The clarify step is now done. You know what to write.
Sometimes the interviewer says, “You decide.” Then pick a simple default. Say it out loud: “I will assume paid orders only.”
05 · Clarify
Find the edge cases
⏱ 6 min · Topic 5 of 13
An edge case is a row that acts in a surprising way. Look for edge cases in the data before you write the query.
This query labels every customer’s orders. A LEFT JOIN keeps Omar, who has no orders:
Label the edge cases in the shopworked example
SQL
Input data
customers5 rows
customer_id
name
country
1
Asha
IN
2
Ben
GB
3
Carla
US
4
Dev
NULL
5
Omar
IN
orders6 rows
order_id
customer_id
status
amount
order_date
101
1
paid
40.00
2026-01-03
102
1
paid
25.00
2026-01-10
103
2
refunded
60.00
2026-01-11
104
3
paid
15.50
2026-01-15
105
3
pending
NULL
2026-01-20
106
4
paid
80.00
2026-01-21
SELECTc.name,c.country,o.order_id,o.status,o.amount,CASEWHENo.order_idISNULLTHEN'no orders'WHENo.status<>'paid'THEN'not paid'WHENc.countryISNULLTHEN'country is NULL'ELSE'normal'ENDASedge_caseFROMcustomersAScLEFTJOINordersASoONo.customer_id=c.customer_idORDERBYc.customer_id,o.order_id;
Result · 7 rows
name
country
order_id
status
amount
edge_case
Asha
IN
101
paid
40.00
normal
Asha
IN
102
paid
25.00
normal
Ben
GB
103
refunded
60.00
not paid
Carla
US
104
paid
15.50
normal
Carla
US
105
pending
NULL
not paid
Dev
NULL
106
paid
80.00
country is NULL
Omar
IN
NULL
NULL
NULL
no orders
This small shop has four edge cases:
Dev has a NULL country. A GROUP BY country puts Dev in one group with a blank country.
Order 103 is refunded and order 105 is pending. Should they count as revenue?
Order 105 has a NULL amount. SUM skips it.
Omar has no orders. An inner join drops him. A LEFT JOIN keeps him.
You do not need to solve every edge case. Name the ones that matter, and say the default you choose. This table shows common edge cases and a default for each:
Edge cases and a default to say
Edge case
A default to say
NULL in a group column
Show it as “unknown” with COALESCE
Statuses other than paid
Leave them out, and say so
Customer with no orders
Use LEFT JOIN if the answer must list everyone
Ties in a top list
Cut at N, with a second ORDER BY column
Many rows for one key
Keep the latest one, with ROW_NUMBER
Dates
Use >= the start and < the day after the end
What to say
“Dev has no country, and order 103 is refunded. I will count paid orders only. I will show a missing country as unknown. Is that right?”
06 · Clarify
Say the grain of each table
⏱ 8 min · Topic 6 of 13
Chapter 1 defined grain: what one row means. Say the grain of every table before you join them.
This table gives the grain of the three shop tables:
Table
One row is
Unique key
customers
one customer
customer_id
orders
one order
order_id
order_items
one product in one order
order_id and product
Test a grain with a count. This query counts the rows and the different orders in order_items:
Orders 101 and 106 have two items each. So there are 7 rows for 5 orders.
What goes wrong in a join
Now a question: “What is the total of the paid orders?” Suppose you join order_items, because you want product names later. This query joins it, then adds up the amounts:
The right total is 160.50, which is 40 + 25 + 15.50 + 80. The query gives 280.50.
Four paid orders go into the join, and six rows come out. Orders 101 and 106 appear twice, so 40 and 80 are added twice.
Chapter 2 called this fan-out. The query shows no error. The question never needed items, so the fix is to not join order_items.
07 · Contract
Write down the result first
⏱ 6 min · Topic 7 of 13
A contract is a written promise about the result. Write it before the SQL, and say it out loud. This is the contract for Question 1:
Columns: name, paid_orders, paid_revenue.
One row per: customer.
Rows that count: paid orders only.
Order: paid_revenue from high to low, then name.
Size: the first 3 rows.
The “then name” part matters when two rows have the same total. This query lists all five customers, including the ones with no paid orders. Question 1 does not need that, but it shows a tie:
Ben and Omar both have 0.00. The second column, name, puts Ben first every time.
COUNT(o.order_id) skips NULL, so Ben and Omar get 0. COALESCE turns their NULL sum into 0.00. Without the second ORDER BY column, the order of tied rows is not fixed.
For the top 3, no tie matters. A customer with no paid order cannot be in the top 3, so the final query can use a plain JOIN.
If the question asked for the top 5, you would need the LEFT JOIN above. Ben and Omar would tie at 0.00, and name would decide.
What to say
“My result has one row per customer: name, paid orders and paid revenue. I sort by revenue, then by name. Tied rows then always come out in the same order.”
08 · Build
Build it in small steps
⏱ 6 min · Topic 8 of 13
Do not write the whole query at once. Write one step, run it, and check the rows. Then add the next step.
A CTE gives each step a name. A CTE is a small result that you name with WITH, as in chapter 4. Step 1 keeps the paid orders:
Each step adds one idea, and each result was checked before the next step. If a number is wrong later, you know which step to open.
09 · Check
Check the result
⏱ 9 min · Topic 9 of 13
A result that looks right can still be wrong. After the query runs, do three checks:
Is the number of rows what you guessed?
Do the parts add up to the whole table?
Does one row match the data when you work it out by hand?
Check 2 is easy to run on the revenue step, before LIMIT. This query adds up the per-customer rows, then compares them with the paid orders in the table:
2 + 1 + 1 = 4 orders. 65.00 + 15.50 + 80.00 = 160.50. Both match the table.
The pairs match, so the grouping step lost no rows and repeated none. Say it out loud: “The grouped total equals the table total.”
Run the same check again after you join, to catch a join that repeats rows.
Check 3: Asha has orders 101 and 102. 40.00 + 25.00 = 65.00. The row for Asha says 65.00, so it matches.
When a check fails
Do not read the final SELECT again and again. Count the rows after each step. This query compares the paid orders with the rows after a join to order_items:
The two options differ only on a tie. Suppose a customer had two orders on the same newest date. This table compares the options:
Question
A: ROW_NUMBER
B: subquery
Two orders on the newest date
Returns one row. The ORDER BY picks it.
Returns both rows
Who decides the winner?
You, in the ORDER BY
Nobody
To explain a choice, say three things:
The options you compared.
What you chose.
When you would choose the other option.
What to say
“Both give the same rows here. I choose ROW_NUMBER, because it returns one row per customer even when two orders share a date. I would use the subquery only if windows were not available.”
11 · Explain
Present your answer
⏱ 5 min · Topic 11 of 13
When the query works and the check passes, explain it. Do not read the code word by word. The interviewer can read it.
Here is the Step 3 query again, with the contract written at the top as comments:
The answer, with its contractworked example
SQL
Input data
customers5 rows
customer_id
name
country
1
Asha
IN
2
Ben
GB
3
Carla
US
4
Dev
NULL
5
Omar
IN
orders6 rows
order_id
customer_id
status
amount
order_date
101
1
paid
40.00
2026-01-03
102
1
paid
25.00
2026-01-10
103
2
refunded
60.00
2026-01-11
104
3
paid
15.50
2026-01-15
105
3
pending
NULL
2026-01-20
106
4
paid
80.00
2026-01-21
-- One row per customer. Paid orders only.-- Sorted by revenue, then name. First 3 rows.WITHpaidAS(SELECTorder_id,customer_id,amountFROMordersWHEREstatus='paid'),revenueAS(SELECTcustomer_id,COUNT(*)ASpaid_orders,SUM(amount)ASpaid_revenueFROMpaidGROUPBYcustomer_id)SELECTc.name,r.paid_orders,r.paid_revenueFROMrevenueASrJOINcustomersAScONc.customer_id=r.customer_idORDERBYr.paid_revenueDESC,c.nameLIMIT3;
Result · 3 rows
name
paid_orders
paid_revenue
Dev
1
80.00
Asha
2
65.00
Carla
1
15.50
Explain each step by what its rows mean. This table shows what you might say for each part:
Explaining the answer
Part
What you might say
paid
“I keep paid orders first, so every later step uses the same rows.”
revenue
“Here I count and add up per customer. This step has one row per customer.”
Final SELECT
“customers has one row per customer, so this join cannot repeat rows. I sort by revenue, then name.”
Check
“The grouped total, 160.50, equals the paid total in the table.”
Assumptions
“I counted paid orders only. A customer with no paid order is left out.”
Watch out
Do not stop at the last line of SQL. End with the check you ran and the assumptions you made. That is the part the interviewer trusts.
12 · Practice
Practice lab
⏱ 12 min · Topic 12 of 13
Each exercise opens in the SQL workspace with its own tables. Run the five steps on each one: clarify, contract, build, check, explain.
The first six labs use chapters 1 to 5 and chapter 13. The last six are optional. Each one names the chapter to read first.
How to practise
Before you write SQL, say the contract out loud: the columns, the grain, the order, and how you treat NULL. Say your check out loud at the end.