learn-sql-with-phoebe / Builder session 10 of 10
Learn SQL with Phoebe · Builder track · Session 10 of 10

Performance and real warehouses

Your queries feel instant - because Daybreak has 33 orders. At work the same query runs on 33 million rows, and suddenly "why is this slow?" is your problem. This final session is the bridge from a browser SQLite tab to production: you will read a query plan, add an index and watch the plan change, and learn exactly which parts of your SQL transfer untouched to PostgreSQL, BigQuery, and Snowflake, and which parts shift at the edges. Same core language, bigger stakes.

🔴 Builder track Practitioners: analysts · DE · DS · PMs Runs in your browser · SQLite Final session · the bridge out
0-5 · Why queries slow down 5-22 · Read a plan · add an index 22-40 · SQLite to warehouses 40-45 · Where next
Part 0

The last mile: from correct to fast, and from toy to production

Nine sessions made you correct. This one makes you production-ready. Two ideas do most of the work. First, performance: a database can answer a query by reading every row (a full scan) or by jumping straight to the rows it needs (an index) - and the difference is invisible at 33 rows and enormous at 33 million. Second, portability: the SQL core you learned is a standard, but each engine speaks a slightly different dialect at the edges. Know the core, flag the edges, and you can walk into any warehouse.

Live - presented in session Self-study - read after class ▶ Live SQL - editable & runnable Official sources covered
★ What you walk out with today The ability to read EXPLAIN QUERY PLAN and tell a full scan from an index lookup, an intuition for the index tradeoff (faster reads, slower writes, more storage), and a dialect-delta table you can keep on your desk for the day your SQLite muscle memory meets a real warehouse. That is the whole Builder track, landed.
Part 1 · covers Mode "Performance Tuning SQL Queries"

Why queries get slow, and reading a plan 7 min live

A query is a request; the engine decides how to fulfill it, and that plan is where speed lives or dies. The most common slow path is a full table scan - reading every single row to find the few you asked for. EXPLAIN QUERY PLAN shows you the engine's chosen strategy before you commit to it.

Full scan: read all 33 (or 33M) rows check every box, keep the one orange match Index lookup: jump straight to the match the index points right at it - the other rows are never touched At 33 rows both feel instant. At 33 million, the scan reads 33M rows and the index reads a handful. That is the gap.
🔍 Click to zoom - a full scan checks every row; an index jumps to the match
LiveRead the plan for a filtered query3 min

EXPLAIN QUERY PLAN does not run the query - it tells you how SQLite would. Run this and read the output: with no index on customer_id, it says it will SCAN the orders table - read every row to find customer 1's orders.

EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE customer_id = 1;
SCAN vs SEARCH In SQLite's plan output, SCAN means "read the whole table" and SEARCH ... USING INDEX means "jump to the rows via an index". Training your eye to spot SCAN on a big, frequently-filtered table is 80% of practical query tuning.
Part 2 · covers Kaggle "Writing Efficient Queries"

Indexes: the tradeoff that makes reads fast 7 min live

An index is a sorted lookup structure on a column - like the index at the back of a book. Create one on the column you filter or join by, and the engine can find matching rows without scanning. It is the single highest-leverage performance move. But it is a tradeoff, not free magic.

LiveAdd an index, then re-read the plan3 min

This box runs on a fresh seeded copy, so creating an index here is safe and resets next run. We build an index on orders(customer_id), then ask for the plan of the same query from Part 1. The output should now say SEARCH ... USING INDEX instead of SCAN.

CREATE INDEX idx_orders_customer ON orders(customer_id);
EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE customer_id = 1;
Real world

The tradeoff, stated plainly. An index makes reads on that column faster - sometimes thousands of times faster at scale. In exchange, every write (INSERT / UPDATE / DELETE) gets a little slower because the index has to be kept in sync, and the index itself costs storage. So you index the columns you filter and join on constantly, not every column. On a write-heavy table, too many indexes can hurt more than they help.

Self-studyWhich columns deserve an index?2 min read

You do not index everything - you index where the engine keeps doing expensive lookups. The reliable candidates are the columns that show up in WHERE and JOIN ... ON over and over on large tables. Primary keys are already indexed for you; foreign keys usually should be, because that is what every JOIN filters on.

  • Index: foreign keys, columns in frequent WHERE filters, columns you sort large results by.
  • Skip: tiny tables (a scan is already instant), columns you rarely filter on, write-hot tables where index upkeep dominates.
  • Measure, do not guess: EXPLAIN QUERY PLAN before and after tells you whether the index is actually used.
Part 3 · the bridge to production warehouses

SQLite to real warehouses 8 min live

The SQL core you learned - SELECT, WHERE, JOIN, GROUP BY, HAVING, CTEs, window functions - is the same in PostgreSQL, MySQL, BigQuery, and Snowflake. What shifts is the edges: date functions, string functions, how you cap rows, how you match case-insensitively. Here is the delta table to keep on your desk.

Laptop · SQLite one file, no server same SQL core different edges Cloud warehouse Postgres · MySQL · BigQuery · Snowflake many machines · billions of rows At scale, SQL runs across a cluster - the query text barely changes, but date/string functions and row limits differ by engine.
🔍 Click to zoom - same SQL core travels; the edges shift by engine
NeedSQLitePostgreSQLMySQLBigQuerySnowflake
Cap rowsLIMIT 5LIMIT 5LIMIT 5LIMIT 5LIMIT 5 (or TOP 5)
Month from datesubstr(d,1,7)DATE_TRUNC('month', d)DATE_FORMAT(d,'%Y-%m')DATE_TRUNC(d, MONTH)DATE_TRUNC('month', d)
String concata || ba || bCONCAT(a, b)CONCAT(a, b)a || b
Case-insensitive matchLIKE (default)ILIKELIKE (default)LOWER(x) = ...ILIKE

Note the old SQL Server TOP 5 style still shows up in legacy code - most modern engines standardized on LIMIT. When in doubt, the engine's own docs settle it; the concepts never change, only the spelling.

Self-studyWhere SQL actually runs at scale2 min read

SQLite runs inside one process reading one file - which is why this whole course fits in a browser tab. A production warehouse like BigQuery or Snowflake spreads one query across many machines, each chewing a slice of a table that may hold billions of rows, then merges the results. You still write the same SELECT ... GROUP BY; the engine handles the parallelism. Your job shifts from "can I express this?" to "will this be cheap and fast?" - which is exactly why plans and indexes (and, in cloud engines, partitioning and clustering) become part of the craft.

  • What transfers: the entire language core from b1-b9. Learn it once.
  • What is new at scale: cost awareness, partitioning, clustering, and reading bigger, engine-specific query plans.
  • The mindset: correct first, then fast. You now have both halves.
Homework · the send-off

Try it yourself - this week ◐ 20-30 min total

Source material

Official sources covered

This session teaches the performance and portability core of the major advanced curricula, run on live data instead of screenshots. Deep engine-specific tuning stays on the official docs. This page covers:

Mode - Advanced SQL: Performance Tuning SQL QueriesParts 1-2 · query plans, full scans, and why queries slow down
Kaggle - Advanced SQL: Writing Efficient QueriesPart 2 · indexes and the read/write tradeoff, run live
Dialect references - PostgreSQL / BigQuery / Snowflake docsPart 3 · the delta table; each engine's own docs stay the source of truth
Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · What does an index do?

An index is a sorted lookup structure: reads on that column get much faster, but every write must keep it in sync and it costs storage. Index the columns you filter and join on.

2 · What does EXPLAIN QUERY PLAN tell you?

It shows the engine's strategy without running the query: SCAN means read every row, SEARCH ... USING INDEX means jump to the matches. Spotting SCAN on big tables is most of practical tuning.

3 · What transfers from SQLite to a production warehouse?

SELECT, WHERE, JOIN, GROUP BY, HAVING, CTEs, and window functions are the same. The edges - date and string functions, row limits, case-insensitive matching - shift by engine.

Builder session 10 cheat sheet · pin this

Why queries slowA full table scan reads every row. Invisible at 33 rows, brutal at 33 million.
EXPLAIN QUERY PLANShows the engine's strategy without running. Look for SCAN (whole table) vs SEARCH USING INDEX.
IndexSorted lookup on a column. CREATE INDEX idx ON orders(customer_id). Turns SCAN into SEARCH.
The tradeoffFaster reads, slower writes, more storage. Index foreign keys and hot filter columns, not everything.
What transfersSELECT / WHERE / JOIN / GROUP BY / HAVING / CTE / windows - identical across engines.
What shiftsDates (substr vs DATE_TRUNC vs EXTRACT), concat (|| vs CONCAT), LIMIT vs TOP, ILIKE.
At scaleWarehouses run one query across many machines. Same SQL, plus cost/partitioning awareness.
Track completeCorrect (b1-b9) + fast (b10). Go answer a real question end to end - that is the point.