C.W.K.
Stream
Lesson 02 of 10 · published

Query Shape First, Indexes Second

~12 min · operations, queries

Level 0Schema Seedling
0 XP0/86 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

The query is the lever

Before adding an index, ask: is the query asking the database to do too much? SELECT * when only three columns are needed, joins on tables you don't actually use, ORDER BY before LIMIT when the order is irrelevant — all of these are query-shape problems no index can fix.

The classic shape problems

  • Selecting columns you don't use (extra IO, defeats covering indexes).
  • Joining tables you don't filter or select from.
  • Aggregating before joining when you could aggregate after.
  • Computing expensive expressions in the SELECT list when they could be cached.
  • Pulling 10,000 rows back to the application to "find the right one" — let the database do the filtering.

The right order of optimisation

  1. Fix the query shape — narrow the columns, narrow the rows, push work into SQL.
  2. Then add the index — if EXPLAIN still shows a problem.
  3. Then add the cache — only if the data is read often and changes rarely.
  4. Then add the materialised view — only if the aggregation is genuinely expensive.

Code

Slim the SELECT, slim the JOIN·sql
-- Before: pulls every column from three tables, then filters in app
SELECT u.*, o.*, p.*
FROM   users u
LEFT JOIN orders o ON o.user_id = u.id
LEFT JOIN products p ON p.id = o.product_id;

-- After: only the columns and rows you need
SELECT u.id, u.name, o.id AS order_id, o.total
FROM   users u
JOIN   orders o ON o.user_id = u.id
WHERE  o.placed_at >= now() - INTERVAL '30 days';
Push aggregation into SQL·sql
-- Before: pull 100k rows, aggregate in Python
rows = db.execute("SELECT total FROM orders WHERE customer_id = %s", (cid,)).fetchall()
total = sum(r[0] for r in rows)

-- After: one query returns one number
total = db.execute(
    "SELECT SUM(total) FROM orders WHERE customer_id = %s", (cid,)
).fetchone()[0]

External links

Exercise

Take the slowest query in your project. Before touching indexes, audit its shape: which columns are returned but unused? Which joins are unfiltered? Which aggregations could move from app code into SQL? Re-measure after each change.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.