Databases
Transactions, isolation levels, JOINs, indexes, and query plans.
19 questions
JuniorTheoryVery commonWhat types of JOIN are there and how do they differ?
What types of JOIN are there and how do they differ?
INNER JOIN keeps only rows matching in both tables. LEFT/RIGHT OUTER JOIN keep all rows from one side, filling NULL for the missing side. FULL OUTER JOIN keeps unmatched rows from both. The condition goes in ON or USING.
Common mistakes
- ✗Thinking
INNER JOINkeeps all rows from the left table - ✗Believing
LEFT JOINdrops unmatched left rows instead of fillingNULL - ✗Assuming
FULL OUTER JOINreturns only matching rows
Follow-up questions
- →When can you use
USINGinstead ofONin aJOIN? - →What does a self-join do and when is it useful?
JuniorTheoryCommonWhat is a database cursor and why use one?
What is a database cursor and why use one?
A cursor is a pointer to a row within a query result set, letting an app fetch and process rows one at a time instead of all at once. It's useful for huge results, but row-by-row work is slower than set-based SQL, so avoid it when possible.
Common mistakes
- ✗Confusing a DB cursor with the GUI mouse pointer
- ✗Believing a cursor is faster than a plain set-based
SELECT - ✗Forgetting that row-by-row processing is slow and should be avoided when possible
Follow-up questions
- →How does a server-side cursor differ from a client-side one?
- →Why can a cursor reduce memory use on a very large result set?
JuniorCodeCommonQuery customers who never placed an order
Query customers who never placed an order
Left-join orders and keep the unmatched rows: SELECT c.id, c.name FROM Customers c LEFT JOIN Orders o ON c.id = o.customer_id WHERE o.customer_id IS NULL. The LEFT JOIN ... IS NULL anti-join keeps exactly the customers with no matching order.
Common mistakes
- ✗Using
!=in a join condition to mean 'no match' - ✗Trusting
NOT INwhen the subquery can contain NULLs - ✗Inner-joining (which drops the no-order customers) then counting zero
Follow-up questions
- →Why can
NOT INgive wrong results when the subquery returns a NULL? - →How does
NOT EXISTSexpress the same anti-join safely?
JuniorCodeCommonWrite a query to find duplicate emails
Write a query to find duplicate emails
Group by the column and keep groups of size > 1: SELECT email FROM Person GROUP BY email HAVING COUNT(*) > 1. You must filter the aggregate with HAVING, not WHERE — WHERE is evaluated before rows are grouped, so it cannot see COUNT(*).
Common mistakes
- ✗Putting
COUNT(*)inWHEREinstead ofHAVING - ✗Thinking
DISTINCTfinds duplicates rather than removing them - ✗Expecting a naive self-join to isolate only the duplicates
Follow-up questions
- →Why can
WHEREnot filter on an aggregate likeCOUNT(*)? - →How would you also return how many times each duplicate email appears?
JuniorTheoryCommonWhat is a transaction, and what does ACID mean?
What is a transaction, and what does ACID mean?
A transaction is a sequence of DB operations run as one unit. ACID: Atomicity (all-or-nothing, else rollback), Consistency (valid states only), Isolation (concurrent txns don't interfere), Durability (committed data survives a crash).
Common mistakes
- ✗Thinking a transaction is a single SQL statement rather than a group of operations
- ✗Confusing Consistency with Isolation, or mixing up what each
ACIDletter guarantees - ✗Believing Durability means encryption rather than surviving a crash
Follow-up questions
- →Which
ACIDproperty does aROLLBACKdirectly enforce? - →How does the database guarantee Durability after a power loss?
JuniorTheoryCommonWhat transaction-control commands do you know?
What transaction-control commands do you know?
COMMIT saves changes permanently, ROLLBACK undoes them, SAVEPOINT marks a point to partially roll back to, and SET TRANSACTION configures properties. They apply to DML (INSERT/UPDATE/DELETE), not to DDL like table creation.
Common mistakes
- ✗Swapping the meaning of
COMMITandROLLBACK - ✗Thinking
SAVEPOINTcommits rather than just marking a rollback point - ✗Assuming these commands control DDL schema changes rather than DML
Follow-up questions
- →Does a DDL statement like
CREATE TABLEcause an implicitCOMMIT? - →How do you roll back to a
SAVEPOINTwithout losing the whole transaction?
MiddleCodeCommonCount employees per department, including empty ones
Count employees per department, including empty ones
Left-join from departments and count the joined key: SELECT d.name, COUNT(e.id) FROM Departments d LEFT JOIN Employee e ON e.dept_id = d.id GROUP BY d.id, d.name. Use COUNT(e.id) (not COUNT(*)) so an empty department shows 0 — COUNT(*) would count the one NULL-padded row as 1.
Common mistakes
- ✗Using
COUNT(*)on aLEFT JOIN, counting the NULL row as 1 - ✗Inner-joining, which drops the empty departments entirely
- ✗Aggregating only
Employee, so zero-staff departments never appear
Follow-up questions
- →Why does
COUNT(e.id)return 0 butCOUNT(*)return 1 for an empty department? - →What must appear in
GROUP BYwhen you selectd.namealongside the count?
MiddleCodeCommonWrite a query for the second-highest salary
Write a query for the second-highest salary
Take the max salary strictly below the overall max: SELECT MAX(salary) FROM Employee WHERE salary < (SELECT MAX(salary) FROM Employee). This returns NULL cleanly when there is no second salary. Alternative: ORDER BY salary DESC LIMIT 1 OFFSET 1 over DISTINCT salaries.
Common mistakes
- ✗Forgetting
DISTINCT, so duplicated top salaries break the offset approach - ✗Putting an aggregate like
MAX()directly in aWHEREclause - ✗Confusing a salary value with its rank/ordinal position
Follow-up questions
- →How would you generalize this to the N-th highest salary?
- →Why does the subquery version return
NULLrather than an empty result for a single salary?
MiddleCodeCommonQuery the highest-paid employee per department
Query the highest-paid employee per department
Rank within each department and keep rank 1: SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn FROM Employee) t WHERE rn = 1. PARTITION BY dept_id restarts the ranking per department — the canonical top-N-per-group pattern.
Common mistakes
- ✗Selecting a non-aggregated
namealongsideMAX(salary)in aGROUP BY - ✗Applying a global
LIMITinstead of a per-partition rank - ✗Filtering on the company-wide max rather than per-department
Follow-up questions
- →How would you return the top 3 earners per department instead of just the top 1?
- →Why does
ROW_NUMBERover a partition outperform a correlated subquery here?
JuniorCodeOccasionalQuery employees who earn more than their managers
Query employees who earn more than their managers
Self-join the table on the manager link and compare salaries: SELECT e.name FROM Employee e JOIN Employee m ON e.manager_id = m.id WHERE e.salary > m.salary. The table is joined to itself, aliasing one copy as the employee and one as the manager via manager_id → id.
Common mistakes
- ✗Comparing to the company average rather than the specific manager
- ✗Joining on department instead of the
manager_id → idlink - ✗Forgetting to alias the two copies of the table distinctly
Follow-up questions
- →How do you also show employees who have no manager (a NULL
manager_id)? - →Why must both sides of the self-join carry distinct table aliases?
MiddleTheoryOccasionalWhat is the difference between EXPLAIN and EXPLAIN ANALYZE?
What is the difference between EXPLAIN and EXPLAIN ANALYZE?
EXPLAIN shows the planner's chosen plan with estimated costs without running the query. EXPLAIN ANALYZE executes it and reports timings and row counts — including side effects, so wrap an INSERT/UPDATE/DELETE in a rolled-back transaction.
Common mistakes
- ✗Swapping which command estimates and which actually executes
- ✗Thinking both only estimate and never run the query
- ✗Forgetting
EXPLAIN ANALYZEexecutes writes and so needs a rolled-back transaction
Follow-up questions
- →What do
BUFFERSandcostmean inEXPLAINoutput? - →Why might the planner's estimated rows differ sharply from the actual rows?
MiddleTheoryOccasionalWhat are transaction isolation levels?
What are transaction isolation levels?
They trade isolation for concurrency, defining which anomalies are allowed: Read Uncommitted (dirty reads), Read Committed (none), Repeatable Read / snapshot (no non-repeatable reads), Serializable (no anomalies). Higher levels cost performance.
Common mistakes
- ✗Believing there is only one isolation level
- ✗Thinking Serializable is the weakest level instead of the strictest
- ✗Assuming higher isolation improves throughput rather than costing performance
Follow-up questions
- →Which isolation level is the default in PostgreSQL?
- →How does Repeatable Read differ from a true snapshot in practice?
MiddleCodeOccasionalWrite a query for the N-th highest salary with ties
Write a query for the N-th highest salary with ties
Rank salaries with a window function and filter on the rank: SELECT salary FROM (SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk FROM Employee) t WHERE rnk = :N. DENSE_RANK gives tied salaries the same rank with no gaps in the numbering.
Common mistakes
- ✗Using
ROW_NUMBERwhere ties must share a rank (useDENSE_RANK) - ✗Relying on
OFFSETwithoutDISTINCT, so ties shift the position - ✗Confusing
RANK(gaps) withDENSE_RANK(no gaps)
Follow-up questions
- →When would you choose
RANKoverDENSE_RANKfor this problem? - →How does
ROW_NUMBERbehave differently fromDENSE_RANKon tied salaries?
MiddleTheoryOccasionalHow do PostgreSQL and MySQL differ?
How do PostgreSQL and MySQL differ?
PostgreSQL is object-relational, more standards-compliant and feature-rich (rich types, CTEs, window queries, server-side cursors). MySQL offers pluggable storage engines like InnoDB and was historically tuned for read speed and key lookups.
Common mistakes
- ✗Claiming
MySQLis fully standards-compliant andPostgreSQLis not - ✗Believing
PostgreSQLhas no transactions - ✗Assuming the two share an identical feature set
Follow-up questions
- →What does
MySQL's pluggable storage engine model let you choose at table level? - →Which
PostgreSQLfeatures make it attractive for analytical workloads?
MiddleCodeOccasionalCompute a running total of salaries by hire date
Compute a running total of salaries by hire date
Use a windowed SUM with an ordered frame: SELECT id, name, salary, SUM(salary) OVER (ORDER BY hire_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total FROM Employee. The frame accumulates from the first row up to the current one.
Common mistakes
- ✗Using
GROUP BY(which collapses rows) instead of a window function - ✗Omitting
ORDER BYin the window, soSUMreturns the grand total per row - ✗Reaching for a self-join when a window frame is far simpler
Follow-up questions
- →Why does omitting
ORDER BYin the window turn the running total into a grand total? - →How does
PARTITION BY dept_idchange the running total's behavior?
MiddleTheoryRareIn standard SQL, do nested transactions exist, and how does a COMMIT behave?
In standard SQL, do nested transactions exist, and how does a COMMIT behave?
In standard SQL/PostgreSQL there are no true nested transactions: a transaction begun inside an active one joins the same outermost transaction. A COMMIT commits that whole outer transaction at once; real nesting is emulated with SAVEPOINTs, which let you partially roll back.
Common mistakes
- ✗Thinking an inner rollback leaves outer changes committed
- ✗Believing each level commits fully and independently
- ✗Assuming a nested
BEGINstarts a real independent transaction instead of joining the outer one
Follow-up questions
- →How does a
SAVEPOINTemulate nested-transaction behavior inPostgreSQL? - →What happens to inner work if the outer transaction itself rolls back?
MiddleTheoryRareWhat does VACUUM do in PostgreSQL?
What does VACUUM do in PostgreSQL?
Under MVCC, updates and deletes leave old row versions ("dead tuples") instead of removing them at once. VACUUM reclaims that space and updates visibility and statistics, so it must run periodically, especially on changed tables.
Common mistakes
- ✗Thinking
VACUUMdeletes live user data - ✗Believing it defragments the disk at the OS level
- ✗Assuming dead tuples are removed instantly without
VACUUM
Follow-up questions
- →How does
VACUUM FULLdiffer from a plainVACUUM? - →What does autovacuum do and when does it trigger?
SeniorTheoryRareWhat concurrency anomalies do isolation levels prevent?
What concurrency anomalies do isolation levels prevent?
Dirty read (seeing uncommitted data) — blocked at Read Committed+. Non-repeatable read (a re-read sees a changed row) — blocked at Repeatable Read+. Phantom read (a re-run query sees new rows) — blocked at Serializable, via 2PL or SSI.
Common mistakes
- ✗Thinking Read Committed prevents phantom reads
- ✗Believing Serializable still allows dirty reads
- ✗Assuming all anomalies vanish at Read Committed
Follow-up questions
- →How does SSI differ from classic two-phase locking?
- →What is a write skew anomaly and which level prevents it?
SeniorTheoryRareHow does MVCC provide snapshot isolation?
How does MVCC provide snapshot isolation?
MVCC keeps multiple row versions; each transaction reads from a consistent snapshot as of its start, so readers never block writers and vice versa, giving repeatable reads without locks. Old versions become dead tuples that VACUUM later reclaims.
Common mistakes
- ✗Believing
MVCClocks every row on read - ✗Thinking it stores only one version, so readers block writers
- ✗Assuming it never produces garbage rows that
VACUUMmust reclaim
Follow-up questions
- →How do
xminandxmaxmark a row version's visibility inPostgreSQL? - →Why can long-running transactions cause table bloat under
MVCC?