interviewpath
  • 0day streak
  • 10total XP
  • Level 1
0/7

Unit 2 · Software engineering · Lesson 6 of 15

SQL fluency

SQL fluency means writing correct queries quickly: joins that keep or drop exactly the right rows, aggregation that counts what you mean, correct NULL handling, readable subqueries and CTEs, window functions, deterministic ordering and cohort or retention analyses. SQL exercises appear in some backend, data and ML engineering interviews, and the same skills are needed to investigate production data.
About 5 hFocus mode

Step 1: Learn the ideas

  • Logical query processing order
  • Inner, outer, cross and self-joins
  • Semi-joins and anti-joins with EXISTS and NOT EXISTS
  • GROUP BY, HAVING and conditional aggregation
  • NULL and three-valued logic
  • Subqueries, correlated subqueries and CTEs
  • Window functions: ranking, offsets and frames
  • Ordering, ties and LIMIT
  • Set operations: UNION, INTERSECT and EXCEPT
  • Date arithmetic, cohorts and retention

Why it matters

SQL is the interface to most production data, and it fails in a specific way: a wrong query usually runs and returns a plausible number. Exercises target exactly the places where intuition breaks, such as a LEFT JOIN that silently becomes an inner join, a NOT IN that returns nothing, a ranking that mishandles ties, or a retention rate with the wrong denominator. Fluency means getting these right on the first attempt and being able to explain why, which is also what you need when an incident forces you to query production data under pressure.

Core ideas

Logical order. A query behaves as if FROM and joins run first, then WHERE, GROUP BY, HAVING, window functions, the SELECT list, DISTINCT, ORDER BY and LIMIT. This explains why WHERE cannot use a SELECT alias or a window result, and why HAVING can filter on aggregates.

Joins. An inner join keeps matching pairs. A LEFT JOIN keeps every left row and fills NULLs where nothing matches; a WHERE filter that tests a right-table column for a value then discards those NULL-extended rows and turns it back into an inner join, so such conditions belong in ON. Joins multiply rows: joining orders to both items and payments double-counts unless you aggregate first. Semi-joins (EXISTS) ask whether a match exists without multiplying; anti-joins (NOT EXISTS, or a LEFT JOIN keeping rows where the right key IS NULL) find rows without one. A CROSS JOIN builds every combination, which is how you produce a row for each user and day before counting.

Aggregation. COUNT(*) counts rows, COUNT(col) counts non-NULL values, and COUNT(DISTINCT col) counts distinct non-NULL values. Aggregates ignore NULLs, and SUM over no rows is NULL rather than 0, so wrap it in COALESCE when zero is the right answer. Conditional aggregation (SUM(CASE WHEN ... THEN 1 ELSE 0 END), or COUNT(*) FILTER (WHERE ...) in PostgreSQL) computes several metrics in one pass.

NULL and three-valued logic. Comparisons with NULL yield UNKNOWN, and WHERE keeps only rows where the condition is TRUE. So status <> 'cancelled' silently drops rows whose status is NULL; write status IS DISTINCT FROM 'cancelled' or add OR status IS NULL. A WHERE x NOT IN (subquery) filter passes no rows at all once the subquery returns a NULL, which is why NOT EXISTS is the safer anti-join.

CTEs and subqueries. CTEs name intermediate results and make multi-step logic readable. PostgreSQL normally folds a side-effect-free, non-recursive CTE that is referenced once into the main query, and MATERIALIZED or NOT MATERIALIZED overrides that choice. Correlated subqueries read clearly but may run once per row; check the plan.

Window functions. A window function computes over related rows without collapsing them. ROW_NUMBER breaks ties arbitrarily, RANK leaves gaps after ties and DENSE_RANK does not; top-N per group is a ranking in a CTE filtered in the outer query. LAG and LEAD compare neighboring rows. With ORDER BY, the default frame runs from the partition start to the current row's last peer, so rows tied on the ordering key share a running total; use a ROWS frame for strict row-by-row totals.

Ordering. Without ORDER BY, row order is unspecified, and ORDER BY on a non-unique key leaves ties in arbitrary order, so add a unique tie-breaker before LIMIT. PostgreSQL sorts NULLs as larger than any value by default (last ascending, first descending); NULLS FIRST and NULLS LAST override it.

Retention. Define the cohort (for example each user's first active day), what counts as retained (any activity in days 1 to 7 after it), and the denominator (every user in the cohort, including those who never returned). Build it in steps:

WITH firsts AS (
  SELECT user_id, MIN(activity_date) AS d0 FROM events GROUP BY user_id
), flags AS (
  SELECT f.user_id, f.d0,
         EXISTS (SELECT 1 FROM events e
                 WHERE e.user_id = f.user_id
                   AND e.activity_date BETWEEN f.d0 + 1 AND f.d0 + 7) AS retained
  FROM firsts f
)
SELECT d0 AS cohort_day, COUNT(*) AS cohort_size,
       AVG(CASE WHEN retained THEN 1.0 ELSE 0.0 END) AS week1_retention
FROM flags GROUP BY d0 ORDER BY d0;

The EXISTS keeps duplicate events from inflating counts, and the per-user CTE keeps the denominator honest.

Common misconceptions

  • "A LEFT JOIN always keeps every left row." Only until a WHERE filter on a right-table column removes the NULL-extended rows; put such conditions in ON.
  • "NOT IN and NOT EXISTS are interchangeable." They differ as soon as the subquery can return NULL: NOT IN then returns no rows.
  • "COUNT(col) counts rows." It skips NULLs; use COUNT(*) for rows and be explicit about what you count.
  • "ROW_NUMBER, RANK and DENSE_RANK differ only cosmetically." They return different rows for top-N with ties; read the requirement for ties first.
  • "Tables have a natural order." Without ORDER BY there is no guaranteed order, and ordering inside a subquery does not order the outer result.
  • "A running SUM over ORDER BY is row by row." The default frame includes peers, so tied rows share a total; use ROWS for row-wise totals.

How to approach it

Refresh with the PostgreSQL tutorial sections on joins, aggregates and window functions, then read the reference sections on joined tables, subquery expressions, comparison operators and window function calls. Practice against a real database so you can inspect intermediate results, and for every exercise write down the grain of each intermediate table (one row per user, per user and day, per order item). In interviews, state the grain and the handling of NULLs and ties before writing, build the query in CTE steps, and check it against a tiny hand-made example with an unmatched row, a NULL and a tie. All levels are expected to write correct queries; senior candidates are also asked about performance and indexes.

Suggested order

  1. 1Read the PostgreSQL tutorial sections “Joins Between Tables” and “Aggregate Functions”, then the reference section “Joined Tables” to pin down ON versus WHERE for outer joins.
  2. 2Read “Comparison Functions and Operators” (IS NULL, IS DISTINCT FROM) and “Subquery Expressions” (EXISTS, IN, NOT IN with NULLs); solve Combine Two Tables, Find Customer Referee, Customers Who Never Order and Students and Examinations.
  3. 3Read “WITH Queries (Common Table Expressions)” and rewrite one multi-step solution as a chain of CTEs, stating the grain of each.
  4. 4Read the window functions tutorial and the “Window Function Calls” section on frames; solve Rank Scores, Consecutive Numbers and Department Top Three Salaries, then Restaurant Growth for frames.
  5. 5Read “Sorting Rows (ORDER BY)” and “LIMIT and OFFSET”; solve Second Highest Salary, paying attention to ties and the empty case.
  6. 6Solve Game Play Analysis IV, then write a weekly cohort retention query over a synthetic events table of your own and check it by hand on ten rows.
  7. 7Work through the SQL 50 plan for breadth, revisiting any problem where a NULL, a tie or a duplicated row surprised you.

Step 2: Check off what you can do

Tap each one you can genuinely do or explain. Each earns 10 XP.

Step 3: Go deeper

  • PostgreSQL documentation: SQL tutorial and SQL language sectionsStart here

    PostgreSQL Global Development Group

    Section: Joined Tables

    Precise semantics for joins, grouping, subquery expressions, comparisons with NULL, ordering and window frames; the reference to check when unsure.

    Open(opens in a new tab)Link responded, 5 Oct 2026
  • PostgreSQL documentation: Transaction IsolationStart here

    PostgreSQL Global Development Group

    Section: Tutorial: Window Functions

    Gentle introduction to PARTITION BY, ORDER BY and frames before the formal syntax section.

    Open(opens in a new tab)Link responded, 5 Oct 2026
  • PostgreSQL documentation: Transaction Isolation

    PostgreSQL Global Development Group

    Section: WITH Queries (Common Table Expressions)

    How CTEs are evaluated, recursive CTEs, and when PostgreSQL folds or materializes a CTE.

    Open(opens in a new tab)Link responded, 5 Oct 2026
  • LeetCode SQL 50 study plan

    LeetCode

    Breadth practice across selection, joins, aggregation, subqueries and window-style problems once the semantics are clear.

    Open(opens in a new tab)Not checkable automatically, 5 Oct 2026
  • CMU 15-445/645 Intro to Database Systems

    Carnegie Mellon University Database Group

    Optional depth: the course’s opening material on the relational model and SQL, for the theory behind joins and aggregation.

    Open(opens in a new tab)Link responded, 5 Oct 2026

Step 4: Practice

Solve in this order. Write each query in CTE steps and test it on a tiny hand-made dataset that includes an unmatched row, a NULL and a tie. Before submitting, state the grain of every intermediate result and how ties and empty results are handled. A week later, re-solve any problem you got wrong without looking at your earlier answer.

Record attempts on each problem’s page (+15 XP, +10 more when solved on your own).

Step 5: Test yourself

Answer each one out loud or on paper before you finish the lesson.

  1. Question 1In what logical order are FROM, WHERE, GROUP BY, HAVING, window functions, SELECT and ORDER BY applied?
  2. Question 2Why does WHERE col <> 'x' drop rows where col is NULL?
  3. Question 3What is the difference between COUNT(*), COUNT(col) and COUNT(DISTINCT col)?
  4. Question 4How do RANK and DENSE_RANK differ on ties, and which does a “top three distinct values” requirement need?
  5. Question 5What is the default window frame when ORDER BY is present, and when does it surprise you?
  6. Question 6How do you produce a zero count for a group that has no matching rows?

You’re done when you can

  • Solve an unseen medium SQL problem that needs a join, an aggregation and a window function in under 20 minutes.
  • Predict before running the output of a NOT IN query whose subquery contains a NULL, and of a LEFT JOIN with a WHERE filter on the right table.
  • Write top-N-per-group queries that keep ties and that break ties, and explain the difference.
  • Write a cohort retention query whose denominator includes users who never returned, and verify it by hand.

Finish the lesson

How confident do you feel?

Lesson complete!

+50 XP

Reviews will come up on the Practice tab so it sticks.

Unlocks next: Relational model and query semantics

Your notes and study time

Log study time