SQL interview practice exam
Fifteen questions across querying, joins, aggregation and window functions.
Which clause is evaluated first?
- SELECT
- FROM
- WHERE
- ORDER BY
Answer: FROM — FROM/JOIN establishes the row set before any filtering or projection.
A LEFT JOIN with a WHERE filter on the right table behaves like:
- LEFT JOIN
- INNER JOIN
- CROSS JOIN
- FULL OUTER JOIN
Answer: INNER JOIN — Unmatched rows have NULL and fail the WHERE, so they are dropped.
Values 100, 90, 90, 80 with RANK() descending produce:
- 1,2,2,3
- 1,2,2,4
- 1,2,3,4
- 1,1,2,3
Answer: 1,2,2,4 — RANK repeats for ties then skips the intervening number.
Which is NULL-safe for "rows with no match"?
- NOT IN
- NOT EXISTS
- != ANY
- LEFT JOIN with WHERE
Answer: NOT EXISTS — NOT EXISTS handles NULLs correctly; NOT IN returns nothing if a NULL appears.
COUNT(*) is 500, COUNT(email) is 480. Explanation?
- Duplicate rows
- 20 NULL emails
- A bad join
- Index missing
Answer: 20 NULL emails — COUNT(col) ignores NULLs.
You need only groups whose SUM exceeds a threshold. Use:
- WHERE
- HAVING
- ON
- FILTER
Answer: HAVING — The condition applies to an aggregate, which exists only after grouping.
Joining orders (1 row) to line_items (many) inflates SUM(total). Best fix?
- SELECT DISTINCT
- Pre-aggregate line_items before joining
- Add an index
- Use RIGHT JOIN
Answer: Pre-aggregate line_items before joining — Pre-aggregation removes the fan-out; DISTINCT cannot fix a summed measure.
WHERE YEAR(order_date) = 2024 ignores the index on order_date because:
- YEAR is slow
- A function on the column prevents a seek
- The index is unused
- Dates cannot be indexed
Answer: A function on the column prevents a seek — Rewrite as a half-open range so the index can be used.
SUM(x) OVER (ORDER BY d) returns a running total rather than the grand total because:
- ORDER BY sorts the output
- ORDER BY inside OVER changes the default frame
- SUM behaves differently in windows
- A PARTITION BY is missing
Answer: ORDER BY inside OVER changes the default frame — With ORDER BY the frame defaults to unbounded preceding through current row.
Which set operator returns rows in the first query but not the second?
- UNION
- INTERSECT
- EXCEPT
- UNION ALL
Answer: EXCEPT — EXCEPT (MINUS in Oracle) is the difference operator.
An index on (region, product) will NOT efficiently serve:
- WHERE region = 'East'
- WHERE product = 'Widget'
- Both columns together
- ORDER BY region
Answer: WHERE product = 'Widget' — Composite indexes are usable on a left-to-right prefix.
In a plan, estimated rows differ wildly from actual rows. Most likely cause?
- Missing index
- Stale statistics
- Too much memory
- Wrong join type
Answer: Stale statistics — The planner is working from out-of-date statistics; refresh them.
Which counts only shipped orders in one pass alongside a total?
- Two separate queries
- COUNT(CASE WHEN status = 'shipped' THEN 1 END)
- COUNT(*) with HAVING
- DISTINCT status
Answer: COUNT(CASE WHEN status = 'shipped' THEN 1 END) — Conditional aggregation pivots inside a single query.
A recursive CTE requires:
- A window function
- An anchor member, UNION ALL, and a recursive member
- An index on the join key
- A temporary table
Answer: An anchor member, UNION ALL, and a recursive member — That three-part structure is what makes the recursion work — plus a termination guarantee.
Which date filter is safest against timestamp edge cases?
- BETWEEN '2024-01-01' AND '2024-01-31'
- >= '2024-01-01' AND < '2024-02-01'
- LIKE '2024-01%'
- YEAR(d)=2024 AND MONTH(d)=1
Answer: >= '2024-01-01' AND < '2024-02-01' — A half-open range avoids both endpoint inclusion problems and function-on-column issues.