Databases & SQL Interview Questions — Joins and Querying Practice
Reviewed by Mark Dickie · Last updated
Databases and SQL are the systems and query languages used to store, model, and retrieve structured data in tables and relationships. For an interview focused on querying and joins, you need to know how inner, left, right, and full outer joins combine rows, how to write aggregations with GROUP BY and HAVING, and how set operators like UNION and EXCEPT compare result sets. You should also be comfortable with subqueries, correlated subqueries, and the difference between WHERE and ON when filtering joined data.
The table below summarizes the join types interviewers ask about most:
| Join type | Returns | What gets dropped |
|---|---|---|
| INNER JOIN | Rows with matching keys in both tables | Non-matching rows from both sides |
| LEFT JOIN | All rows from the left table, matched or not | Nothing from the left; unmatched right rows become NULL |
| RIGHT JOIN | All rows from the right table, matched or not | Nothing from the right; unmatched left rows become NULL |
| FULL OUTER JOIN | Every row from both tables | Nothing; unmatched sides are filled with NULL |
| CROSS JOIN | Cartesian product of both tables | Nothing — no ON clause |
What does a SQL querying interview test?
Interviewers want to see that you can translate a business question into a correct query without trial-and-error in the IDE. They look for clean column aliases, correct join direction, awareness of duplicate rows, and handling of NULLs in filters and aggregations. Performance reasoning — knowing when an index helps and when a subquery should become a join — separates mid-level from senior candidates.
How should you prepare for join-heavy interview questions?
- Write five queries from memory that each use a different join type, and predict the row count before you run them.
- Practice combining GROUP BY with a join, then filter groups with HAVING instead of WHERE.
- Write the same result set three ways: a correlated subquery, a CTE, and a join — then compare which the interviewer finds clearest.
- Drill NULL behavior in WHERE clauses, especially the difference between
WHERE col = NULLandWHERE col IS NULL. - Time yourself writing a self-join query, since self-joins appear often in org-hierarchy and consecutive-events problems.
Use the quiz below to test yourself on these patterns with real interview questions.
Key facts
- Tarmac's Databases & SQL interview questions cover 10 questions at difficulty 2–4 of 5.
- Tarmac tracked 8,648 job postings asking for Databases & SQL in August 2026.
- Roles asking for Databases & SQL advertise a median base salary of US$166,400, across 1,647 job postings as of August 2026.
- Tarmac last reviewed these Databases & SQL interview questions on 14 September 2026.
At a glance
| Questions | 10 |
|---|---|
| Difficulty | 2–4 of 5 |
| Formats | True / false, Code output, Fill in the blank, Multiple choice, Coding exercise, Find the bug |
| Interactive | 4 run your code against tests, in the app |
What you'll review
- joins
Practice questions
Try one before you open the answer. Pick an option and press Check; it's marked on the spot.
Databases & SQL/querying/joins
For an INNER JOIN, swapping which table is written first (A JOIN B vs B JOIN A) changes which rows appear in the result.#
Options
Show answer
False. INNER JOIN is commutative with respect to its result set, so A JOIN B and B JOIN A return the same rows (column ordering aside). Swapping which table is written first changes nothing about which rows appear. This is not true for outer joins, where LEFT JOIN and RIGHT JOIN keep different unmatched sides.
INNER JOIN is commutative with respect to its result set: A INNER JOIN B ON … and B INNER JOIN A ON … return the same rows (column ordering aside). This is not true for outer joins — LEFT JOIN and RIGHT JOIN keep different unmatched sides.
Databases & SQL/querying/joins
customers(id, name) has (1, Ann), (2, Bo), (3, Cy). orders(customer_id) has rows for customer_id 1 and 1. What does this query return?#
SELECT c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.customer_id IS NULL
ORDER BY c.name;Options
Show answer
Bo, Cy
The LEFT JOIN keeps all customers; Bo and Cy have no matching order, so their o.customer_id is NULL. The WHERE o.customer_id IS NULL filter then keeps exactly those unmatched rows, returning Bo and Cy. This anti-join pattern is the standard way to find rows in one table with no counterpart in another.
Databases & SQL/querying/joins
To keep every row from the left table even when there is no match, use customers c _____ JOIN orders o _____ o.customer_id = c.id.#
Show answer
To keep every row from the left table even when there is no match, use customers c **LEFT** JOIN orders o **ON** o.customer_id = c.id.
LEFT JOIN (synonym LEFT OUTER JOIN) preserves all left-hand rows, padding unmatched right-hand columns with NULL. The ON clause states the join predicate that decides which rows match — distinct from a later WHERE, which filters the joined result.
Databases & SQL/querying/joins
A LEFT JOIN between customers (left) and orders (right) returns a customer who has placed no orders. What appears in that row's orders columns?#
Options
Show answer
Every orders column holds NULL. A LEFT JOIN keeps each left-hand row even when no right-hand row matches the ON condition, filling the unmatched right-hand columns with NULL rather than omitting the row or substituting 0 or empty strings. This is exactly why a WHERE orders.id IS NULL filter after a LEFT JOIN finds customers who have placed no orders.
A LEFT JOIN keeps every left-hand row even when no right-hand row matches the ON condition; the unmatched right-hand columns are filled with NULL. This is exactly why a WHERE orders.id IS NULL filter after a LEFT JOIN finds customers with no orders.
Databases & SQL/querying/joins
customers(id, name) has (1, Ann), (2, Bo), (3, Cy). orders(customer_id, amount) has (1, 10), (1, 20), (2, 5). How many rows does this query return?#
SELECT c.name, o.amount
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id;Options
Show answer
3 rows
An INNER JOIN emits one row per matching pair. Ann (id 1) matches two orders and Bo (id 2) matches one, giving 3 rows; Cy (id 3) has no orders and is excluded entirely. The result is (Ann, 10), (Ann, 20), (Bo, 5).
Databases & SQL/querying/joins
The customers table has columns id (INTEGER) and name (TEXT). The orders table has columns id (INTEGER), customer_id (INTEGER referencing customers.id), and total (INTEGER). Write a query returning the customers who have placed NO orders at all, with a single column name, ordered by name ascending. Use an anti-join pattern (LEFT JOIN ... IS NULL or NOT EXISTS) — an inner join cannot find these rows.#
Starter code
-- Find customers with zero orders via LEFT JOIN ... IS NULL or NOT EXISTS.
-- Return: name, ordered by name ASC.
SELECT
Your solution must pass
- two of four customers never ordered
This one is written and run, not read. Solve it in the app and your code is executed against these tests and the hidden ones.
Databases & SQL/querying/joins
The employees table has columns id (INTEGER), name (TEXT), salary (INTEGER), and manager_id (INTEGER referencing employees.id, NULL for employees with no manager). Write a query returning each employee who earns strictly more than their direct manager, with columns name and salary (the employee's own name and salary), ordered by name ascending. You will need to join the table to itself.#
Starter code
-- Self-join employees (as employee and as manager) on manager_id.
-- Return: name, salary of employees paid more than their manager, ordered by name ASC.
SELECT
Your solution must pass
- two employees out-earn their managers; top-level employees excluded
This one is written and run, not read. Solve it in the app and your code is executed against these tests and the hidden ones.
Databases & SQL/querying/joins
This is meant to be a LEFT JOIN listing every customer and their order count, but customers with no orders are dropped. Which line causes that?#
SELECT c.name, COUNT(o.id) AS order_count
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'shipped'
GROUP BY c.name;Show answer
The bug is on line 4.
A WHERE predicate on the right-hand table runs after the join, and o.status = 'shipped' is never true for the NULL columns of an unmatched customer, so those rows are filtered out — silently turning the LEFT JOIN into an inner join. The fix is to move the condition into the join itself (LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'shipped') so unmatched customers survive.
Databases & SQL/querying/joins
Two payment ledgers need reconciling. The bank_ledger table has columns payment_ref (TEXT, unique) and amount (INTEGER); the book_ledger table has the same two columns. Write a single query using FULL OUTER JOIN on payment_ref that returns one row per payment present in EITHER ledger, with columns bank_ref, bank_amount, book_ref, book_amount (the bank-side pair is NULL when the payment is missing from the bank ledger, and likewise for the book side). Order by COALESCE(bank_ref, book_ref) ascending.#
Starter code
-- FULL OUTER JOIN the two ledgers on payment_ref so unmatched rows on
-- either side survive with NULLs. Return: bank_ref, bank_amount, book_ref,
-- book_amount, ordered by COALESCE(bank_ref, book_ref) ASC.
SELECT
Your solution must pass
- matched, bank-only, book-only, and amount-mismatch rows all appear
This one is written and run, not read. Solve it in the app and your code is executed against these tests and the hidden ones.
Databases & SQL/querying/joins
The customers table has columns id (INTEGER) and name (TEXT). The orders table has columns id (INTEGER), customer_id (INTEGER referencing customers.id), and price (INTEGER). Return every order priced strictly above ITS OWN customer's average order price, with columns order_id (the order's id), name (the customer's name), and price, ordered by order_id ascending. Do NOT use a correlated subquery in the WHERE clause — instead JOIN against a grouped derived table (or CTE) of per-customer averages. The average is only compared against, never output, so fractional averages are fine.#
Starter code
-- Build a derived table of per-customer AVG(price), then JOIN orders to it
-- and keep orders priced above their customer's average.
-- Return: order_id, name, price, ordered by order_id ASC.
SELECT
Your solution must pass
- each customer's above-average orders, compared per customer not globally
This one is written and run, not read. Solve it in the app and your code is executed against these tests and the hidden ones.
Sources
The official documentation these questions are checked against:
Related interview questions
Job market
See databases-sql salaries and hiring demand from live job postings.
Practise these until they stick
That's every question we hold on this topic, and the page marks what you pick. What it can't do is remember. A free account keeps every answer, and what you miss comes back until it's right: after a day, then at longer gaps.
Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan