How to Read a SQL Query You Did Not Write — A Step-by-Step Guide

You inherit a codebase, open a report generator, and find a 30-line SQL query with three JOINs, a GROUP BY, and a subquery buried in the middle. Nobody left comments. This happens constantly — debugging a slow report, reviewing a pull request, or just trying to understand what data a dashboard is actually pulling. This guide gives you a repeatable method for reading unfamiliar SQL: understanding the gap between how a query is written and how it actually executes, and a worked example that walks through JOIN, WHERE, GROUP BY, and ORDER BY clause by clause.

Reading Order vs Execution Order

The single most useful thing to internalize about SQL is that the order you write it in is not the order it runs in. You write SELECT first, but the database engine does not evaluate it first.

Written order:                Execution order:
1. SELECT                     1. FROM
2. FROM                       2. JOIN
3. JOIN                       3. WHERE
4. WHERE                      4. GROUP BY
5. GROUP BY                   5. HAVING
6. HAVING                     6. SELECT
7. ORDER BY                   7. ORDER BY
8. LIMIT                      8. LIMIT

This is why a query like SELECT price * qty AS total FROM orders WHERE total > 100 fails in most databases — at the point WHERE runs, SELECT has not executed yet, so the alias total does not exist. It also explains why WHERE filters individual rows before grouping, while HAVING filters groups after grouping — they run at different stages entirely. When you read an unfamiliar query, mentally re-order it into execution order first: figure out the source tables, then the row filter, then the grouping, then what gets selected, then the final sort.

Worked Example: A Query With JOIN, WHERE, GROUP BY, and ORDER BY

Here is a realistic query you might find in an internal reporting tool:

SELECT
  c.country,
  COUNT(o.id) AS order_count,
  SUM(o.total_amount) AS revenue
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'completed'
  AND o.created_at >= '2026-01-01'
GROUP BY c.country
HAVING COUNT(o.id) >= 10
ORDER BY revenue DESC
LIMIT 5;

Reading this in execution order, not written order:

  • FROM orders o — start with the orders table, aliased as "o"
  • JOIN customers c ON o.customer_id = c.id — attach matching customer rows to each order, aliased as "c", so we get order data and customer data on the same row
  • WHERE o.status = 'completed' AND o.created_at >= '2026-01-01' — throw away any joined row that isn't a completed order from this year, before any grouping happens
  • GROUP BY c.country — collapse all remaining rows into one row per country
  • HAVING COUNT(o.id) >= 10 — after grouping, drop any country group with fewer than 10 orders
  • SELECT c.country, COUNT(o.id), SUM(o.total_amount) — now compute what to actually return for each surviving group: the country name, the order count, and the revenue total
  • ORDER BY revenue DESC — sort the resulting rows from highest revenue to lowest
  • LIMIT 5 — keep only the top 5 rows

In plain English: "for each country with at least 10 completed orders since the start of 2026, show the order count and total revenue, and give me the top 5 countries by revenue." Notice that reading it in execution order made the logic obvious, while reading it top-to-bottom as written would have forced you to jump between the SELECT list and the FROM/WHERE clauses to figure out what "revenue" even refers to.

Tips for Mentally Parsing Nested Subqueries

Subqueries trip people up because they break the "read top to bottom" instinct. The fix is to read inside-out instead:

  • Find the innermost query first — the one with no other SELECT nested inside it
  • Treat that innermost query as if it were a regular table that already exists, and note what columns it produces
  • Move one level out and read that query as if it were querying a normal table with those column names
  • Repeat until you reach the outermost query
SELECT name, total_spent
FROM (
  SELECT customer_id, SUM(total_amount) AS total_spent
  FROM orders
  GROUP BY customer_id
) spend
JOIN customers ON customers.id = spend.customer_id
WHERE total_spent > 1000
ORDER BY total_spent DESC;

Read the inner query first: "for each customer, sum their order totals." That produces a virtual table called spend with two columns: customer_id and total_spent. Now read the outer query as if spend were a real table: "join it to customers, keep only those who spent over 1000, sort by spend descending." Two simple steps instead of one confusing block.

Common Query Patterns to Recognize

  • Lookup JOIN — a small JOIN just to pull in a readable name for an ID, e.g. joining a status_id to a statuses table
  • Aggregation with GROUP BY — counting, summing, or averaging rows per category, almost always followed by a HAVING or ORDER BY
  • Self-JOIN — a table joined to itself, usually to compare rows to each other (e.g. an employee to their manager in the same table)
  • EXISTS subquery — used instead of a JOIN when you only need to check "does at least one matching row exist," not pull its columns
  • Window function over PARTITION BY — ranking or running totals per group without collapsing rows the way GROUP BY does

Frequently Asked Questions

What is the difference between SQL reading order and execution order?

SQL is written SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY — but it executes FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY. Reading a query in execution order makes the logic far easier to follow.

Is there a free tool to explain a SQL query in plain English?

Yes. The Dev Brains AI SQL Explainer breaks down any query clause by clause into plain English, for free.

How do I read a SQL query with a nested subquery?

Start from the innermost subquery and work outward, treating each one as a temporary table. Reading inside-out is much easier than trying to parse the whole thing top-to-bottom.

Try the Free SQL Query Explainer

Paste any SQL query and get a clause-by-clause explanation in plain English. No signup, no cost.

Related articles