The Problem
Tutorials hand you a clean df = pd.read_csv(...). Real jobs hand you a database with 40 tables and a Slack message: "can you pull active users who churned last quarter, by plan?" SQL appears in ~26% of ML engineer postings and in nearly every take-home, because the data for your features lives in a warehouse, not a CSV. An ML engineer who can't write SQL is blocked on someone who can.
This lesson builds the query muscle: turn a question in English into one correct query.
The Concept
Why SQL Matters for ML Engineers
Most ML tutorials start with pd.read_csv("data.csv") — a clean file on disk. In production, your data lives in a warehouse (Snowflake, BigQuery, Redshift) spread across dozens of tables. The features you need for your model — "user's 30-day spending," "average session duration," "days since last purchase" — don't exist as columns in a table. They exist as SQL queries you have to write. SQL is how you shape raw event data into the model-ready feature table.
SQL also appears in ~26% of ML engineer job postings and in nearly every take-home assignment, because the ability to extract and transform data is a prerequisite for building models on real data.
The Logical Order of Query Execution
Every SQL query is the same six clauses, but they run in a specific logical order that is different from how you write them:
FROM which table(s)
WHERE filter rows (before grouping)
GROUP BY collapse rows into groups
HAVING filter groups (after aggregation)
SELECT choose / compute columns
ORDER BY sort
LIMIT cut
You write SELECT first, but the database evaluates it almost last. This ordering explains most beginner bugs. The most common one: trying to filter on an aggregate in WHERE. WHERE runs before GROUP BY, so SUM() hasn't been computed yet — you need HAVING, which runs after GROUP BY. Understanding this execution order transforms SQL from "memorized syntax" to "a pipeline you can reason about."
You want to find users whose total spending exceeds $1000. You write: WHERE SUM(amount) > 1000. It fails. Why?
WHERE runs before GROUP BY, so SUM() hasn't been computed yet. HAVING runs after GROUP BY and can filter on aggregates: HAVING SUM(amount) > 1000.
We'll use one schema throughout:
users(user_id, signup_date, plan, country)
events(event_id, user_id, event_type, amount, created_at)
Unlock the full lesson
You've read the first 2 sections. The rest of this lesson covers Build It, Use It, Ship It, Evaluation, Exercises, Key Terms, Common Pitfalls, Interview Framing — plus a hands-on lab, quiz, and project artifact.
Create a free account to unlock Phase 0 and Phase 1 of every course — no credit card.