SQL • SQL FOUNDATIONS

SQL Logical Order — Describe the logical order of SQL operations (FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT) (conceptual)

Understanding how the database engine processes your query, regardless of how you write it.

Historical Context & Motivation

When Edgar F. Codd published his seminal 1970 paper on the relational model of data, he envisioned a world where users could describe what data they wanted rather than how to retrieve it. This declarative philosophy became the foundation of SQL (Structured Query Language), which was first implemented at IBM as SEQUEL in the mid-1970s. A critical implication of this declarative design is that the order in which a programmer writes SQL clauses is not the order in which the database engine evaluates them. The engine follows a well-defined logical processing order that determines which rows are considered, how they are grouped, and what ultimately appears in the result set.

1970
Codd's Relational Model
Edgar F. Codd publishes "A Relational Model of Data for Large Shared Data Banks," establishing the theoretical basis for relational databases and declarative query languages.
1974
SEQUEL at IBM
Donald Chamberlin and Raymond Boyce develop SEQUEL (later renamed SQL) at IBM's San Jose Research Laboratory, introducing the familiar clause-based syntax: SELECT, FROM, WHERE.
1986
ANSI SQL Standard
SQL becomes an ANSI standard (SQL-86), formalizing the language's syntax and, implicitly, the logical processing order that all conforming engines must respect.
1992
SQL-92 & GROUP BY / HAVING
The SQL-92 standard refines aggregation semantics, codifying the relationship between GROUP BY, HAVING, and SELECT and making the logical order explicitly relevant for query correctness.
2003–Present
Window Functions & Modern SQL
SQL:2003 introduces window functions, adding new phases to logical processing. Modern optimizers reorder physical execution while preserving logical semantics, reinforcing the importance of understanding the logical order.

The fundamental question this lesson addresses is deceptively simple: in what order does the database engine logically process the clauses of a SQL query? The syntactic order—SELECT first, then FROM, then WHERE—is a notational convention for human readability. The logical order starts with FROM, then WHERE, and evaluates SELECT near the end. Failing to understand this distinction leads to common errors: referencing column aliases in the WHERE clause, misusing aggregate functions, or being confused by the behavior of HAVING versus WHERE. Mastering the logical order transforms SQL from a trial-and-error exercise into a principled, predictable tool.

Core Principles & Definitions

Before dissecting each phase, it is essential to establish the conceptual framework that governs SQL query evaluation. SQL is a declarative language, meaning you specify the desired result rather than the procedural steps to compute it. The database management system (DBMS) translates your declaration into a physical execution plan, but the logical processing order defines the conceptual sequence of operations that guarantees the result's correctness. Every SQL engine—PostgreSQL, MySQL, SQL Server, Oracle—must produce results consistent with this logical order, even though its optimizer may physically execute steps in a completely different sequence for performance.

1

Declarative vs. Procedural

SQL tells the engine what to retrieve. The logical order is the engine's conceptual recipe for transforming that declaration into a result set. You never write loops or index lookups—the engine handles that.
2

Logical vs. Physical Order

The logical order is an abstract contract: given these clauses, the result must be as if they were executed in this sequence. The physical execution plan—decided by the query optimizer—may reorder, parallelize, or skip steps entirely.
3

Scope & Visibility

Each phase can only reference information available from earlier phases. A column alias defined in SELECT is invisible to WHERE because WHERE executes first. This scoping rule is the most common source of SQL errors.
4

Set-Based Thinking

Each logical phase takes a set of rows as input and produces a new set of rows as output. FROM builds the initial set, WHERE filters it, GROUP BY partitions it, and so on. SQL operates on entire sets, not individual rows.
5

The Seven-Phase Pipeline

The canonical logical order is: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT. Each phase narrows, reshapes, or reorders the intermediate result before passing it to the next phase.
KEY TAKEAWAY
Think of the logical order like a manufacturing assembly line. The raw materials arrive at the dock (FROM), are inspected for defects (WHERE), sorted into bins (GROUP BY), bins are inspected (HAVING), labels are printed (SELECT), boxes are arranged on the shelf (ORDER BY), and finally you load only what fits in the truck (LIMIT). You don't print labels before you know which bins passed inspection—just as SQL doesn't evaluate SELECT before HAVING.

Visual Explanation — The Logical Pipeline

The left column shows the logical processing order as a pipeline: each phase receives the output of its predecessor. The right column maps each clause to its position in the familiar syntactic order. The key mismatch is SELECT, which we write first but which is logically evaluated fifth.

The diagram above illustrates the central insight of this lesson. When you write a SQL query, you begin with the SELECT keyword, but the engine begins its logical evaluation with FROM. Each phase transforms an intermediate virtual table into a new virtual table, passing it down the pipeline. The FROM clause constructs the broadest possible set of candidate rows (the Cartesian product of all joined tables, refined by join conditions). The WHERE clause then eliminates rows that fail the filter predicate. GROUP BY collapses the surviving rows into groups, after which HAVING filters entire groups. Only then does SELECT evaluate expressions and assign aliases. Finally, ORDER BY sorts the result and LIMIT truncates it.

Deep Dive — Each Phase Explained

Phase 1: FROM / JOIN

The FROM clause is the logical starting point of every query. When multiple tables are specified, the engine conceptually forms their Cartesian product—every combination of rows from each table. If table A has m rows and table B has n rows, the Cartesian product contains m × n rows. JOIN conditions (ON clauses) then filter this product to retain only meaningful row pairings. OUTER JOINs additionally preserve unmatched rows from one or both sides, padding missing columns with NULLs. The output of this phase is a single virtual table that contains every column from every source table.

Phase 2: WHERE

The WHERE clause applies a Boolean predicate to each individual row in the virtual table produced by FROM. Rows for which the predicate evaluates to TRUE survive; rows evaluating to FALSE or UNKNOWN (due to NULLs) are discarded. A critical rule is that aggregate functions cannot appear in WHERE because groups have not yet been formed. You cannot write WHERE COUNT(*) > 5 — the engine has no concept of groups at this stage. This restriction is a direct consequence of the logical order.

Phase 3: GROUP BY

The GROUP BY clause partitions the filtered rows into groups based on one or more grouping columns. Within each group, all rows share identical values for the grouping columns. Once grouping occurs, the virtual table transitions from a table of individual rows to a table of groups. From this point forward, any column referenced in SELECT or HAVING must either appear in the GROUP BY list or be wrapped in an aggregate function (SUM, COUNT, AVG, MAX, MIN, etc.). This constraint ensures that each expression produces exactly one value per group.

Phase 4: HAVING

The HAVING clause is the group-level counterpart of WHERE. It applies a Boolean predicate to each group, and groups that fail are eliminated. Unlike WHERE, HAVING can reference aggregate functions because groups already exist at this stage. For example, HAVING COUNT(*) > 5 retains only groups with more than five members. A common beginner mistake is using HAVING for row-level filters — this is semantically incorrect and often less efficient than using WHERE, because HAVING runs after grouping has already been performed.

Phase 5: SELECT

The SELECT clause is evaluated fifth, despite appearing first in the syntax. It determines which columns or expressions appear in the output. Column aliases are defined here via AS, which is precisely why those aliases are invisible to WHERE, GROUP BY, and HAVING—those clauses have already been evaluated. However, aliases defined in SELECT are visible to ORDER BY and LIMIT, which run later. If DISTINCT is specified, duplicate rows are eliminated at this stage.

Phase 6: ORDER BY

The ORDER BY clause sorts the result set. Because it executes after SELECT, it can reference column aliases and ordinal positions (e.g., ORDER BY 2 refers to the second column in the SELECT list). ORDER BY is the only clause that can reference columns not in the SELECT list (in most SQL dialects), because the full virtual table is still available from earlier phases. Without ORDER BY, SQL makes no guarantees about row order; the result is technically an unordered set.

Phase 7: LIMIT / OFFSET

The LIMIT clause (called TOP in SQL Server or FETCH FIRST in ANSI SQL:2008) truncates the sorted result set to a specified number of rows. OFFSET, when used alongside LIMIT, skips a given number of rows before returning results, enabling pagination. Because LIMIT is the final logical phase, it operates on the fully sorted, fully projected result. Using LIMIT without ORDER BY is generally meaningless, since the set of rows retained is nondeterministic.

Alias Visibility & Scope Rules

One of the most practical consequences of the logical order is the visibility of column aliases. Since SELECT is evaluated in phase 5, any alias you create there (for example, SELECT price * quantity AS total_cost) does not exist during earlier phases. This explains why the following query is illegal in standard SQL: SELECT price * quantity AS total_cost FROM orders WHERE total_cost > 100. The WHERE clause (phase 2) cannot see total_cost because it hasn't been defined yet. You must instead repeat the expression: WHERE price * quantity > 100.

This matrix shows which types of references are available at each logical phase. Column aliases become available only at SELECT (phase 5) and are therefore visible to ORDER BY and LIMIT but not to WHERE, GROUP BY, or HAVING. Aggregate functions become meaningful only after GROUP BY (phase 3), which is why they cannot appear in WHERE.
Alias and aggregate visibility by clause
ClauseCan Use Column Aliases?Can Use Aggregates?Reason
FROMNoNoOnly raw table/column names are resolved.
WHERENoNoSELECT hasn't run; groups don't exist yet.
GROUP BYNoImplicitlyGroups are being formed; aggregates computed here.
HAVINGNoYesGroups exist; SELECT aliases still not defined.
SELECTDefined hereYesAliases are created; aggregates evaluated.
ORDER BYYesYesRuns after SELECT; all aliases visible.
LIMITYesYesRuns last; operates on final sorted result.
Dialect Exception: MySQL
MySQL allows column aliases in GROUP BY and HAVING as a non-standard extension. While convenient, relying on this behavior produces queries that are not portable to PostgreSQL, SQL Server, or Oracle. For academic and production work, follow the ANSI standard and assume aliases are invisible before ORDER BY.

Worked Example — Tracing the Logical Order

Consider the following query against a hypothetical orders table with columns customer_id, product_category, amount, and order_date:

📝 Query
SELECT product_category, SUM(amount) AS total_sales FROM orders WHERE order_date >= '2024-01-01' GROUP BY product_category HAVING SUM(amount) > 10000 ORDER BY total_sales DESC LIMIT 5;
Tracing the Logical Processing Order
1
Step 1 — FROMThe engine identifies the source table: orders. All rows and all columns of orders form the initial virtual table. If there were JOINs, they would be resolved here.
Virtual table VT₁ = all rows from orders.
2
Step 2 — WHEREThe predicate order_date >= '2024-01-01' is evaluated against each row in VT₁. Every row with an order_date before January 1, 2024 is discarded. Note that we cannot reference total_sales here because SELECT hasn't executed.
Virtual table VT₂ = rows from 2024 onward.
3
Step 3 — GROUP BYThe surviving rows in VT₂ are partitioned by product_category. If categories include 'Electronics', 'Clothing', 'Books', etc., each becomes a separate group. The virtual table is now a table of groups, not individual rows.
Virtual table VT₃ = one group per distinct product_category.
4
Step 4 — HAVINGThe aggregate predicate SUM(amount) > 10000 is evaluated for each group. Groups whose total sales are $10,000 or less are eliminated. This is the group-level filter—analogous to WHERE for individual rows.
Virtual table VT₄ = only high-revenue categories.
5
Step 5 — SELECTThe engine now evaluates the SELECT expressions: product_category (a grouping column, so one value per group) and SUM(amount) AS total_sales (the aggregate, now given the alias total_sales). The result set is projected down to these two columns.
Virtual table VT₅ = two-column result with alias total_sales now defined.
6
Step 6 — ORDER BYThe result is sorted by total_sales DESC. Because ORDER BY runs after SELECT, the alias total_sales is now visible and can be used. The highest-revenue categories appear first.
Virtual table VT₆ = sorted by total_sales descending.
7
Step 7 — LIMITOnly the first 5 rows of the sorted result are retained. This gives us the top 5 product categories by revenue since January 2024.
Final result: top 5 product categories by 2024 revenue exceeding $10,000.

Common Pitfalls & WHERE vs. HAVING

Understanding the logical order is not merely academic; it directly prevents a class of common SQL bugs and performance anti-patterns. The table below catalogs the most frequent mistakes that arise from ignoring or misunderstanding the processing sequence.

Common SQL mistakes caused by misunderstanding logical order
PitfallIncorrect UsageCorrect Approach
Alias in WHEREWHERE total_cost > 100WHERE price * qty > 100
Aggregate in WHEREWHERE COUNT(*) > 5HAVING COUNT(*) > 5
Row filter in HAVINGHAVING status = 'active'WHERE status = 'active'
LIMIT without ORDER BYSELECT * FROM t LIMIT 10Add ORDER BY for deterministic results
Non-grouped column in SELECTSELECT name, SUM(x) ... GROUP BY deptAdd name to GROUP BY or use an aggregate
WHERE vs. HAVING — THE DEFINITIVE RULE
Think of WHERE as a bouncer at the door of a nightclub—it decides who gets in before anyone sits down at tables (groups). HAVING is the manager who walks through the club after groups have formed and asks certain tables to leave. A filter on a raw column (e.g., status = 'active') always belongs in WHERE because it eliminates rows before the expensive grouping operation. A filter on an aggregate (e.g., COUNT(*) > 5) must go in HAVING because the aggregate can only be computed after groups exist. Misplacing a row-level filter in HAVING forces the engine to group unnecessary rows, degrading performance.

Connection to Query Optimization & Advanced SQL

The logical order establishes correctness, but real-world database engines employ query optimizers that rewrite and reorder operations for efficiency. A cost-based optimizer might push a WHERE predicate into a JOIN condition (predicate pushdown), evaluate LIMIT before fully sorting (top-N optimization), or skip GROUP BY entirely when an index already provides sorted, aggregated data. The guarantee is that the final result is indistinguishable from what the logical order would produce. Understanding the logical order thus gives you the mental model to predict what the optimizer is allowed to do and—crucially—what errors it cannot silently fix.

Logical order vs. advanced SQL concepts
ConceptLogical Order PerspectiveAdvanced / Physical Perspective
Subqueries & CTEsEach subquery follows the same 7-phase pipeline internally.Optimizer may inline CTEs or materialize them; logical order is per query block.
Window FunctionsEvaluated after SELECT, before ORDER BY (phase 5.5 conceptually).Can share sort operations with ORDER BY for efficiency.
DISTINCTLogically applied during SELECT (phase 5), removes duplicate rows.May be implemented via hash or sort-based deduplication.
Predicate PushdownWHERE logically runs in phase 2, after FROM.Optimizer pushes predicates into index scans or JOIN conditions for early elimination.
UNION / INTERSECT / EXCEPTEach operand follows the full pipeline; set operation combines results.ORDER BY and LIMIT apply to the combined result, not individual operands.

As you advance into topics like window functions, recursive CTEs, and query execution plans (EXPLAIN), the logical order remains your foundational mental model. Window functions, for example, are logically evaluated after WHERE and GROUP BY but before ORDER BY—which is why they can reference columns filtered by WHERE and aggregated by GROUP BY, but not values sorted by ORDER BY. Every new SQL feature slots into this pipeline; knowing the pipeline means you can reason about any query, no matter how complex.

Practice Problems

PROBLEM 1CONCEPTUAL
A student writes SELECT department, AVG(salary) AS avg_sal FROM employees WHERE avg_sal > 50000 GROUP BY department; and receives an error. Explain, using the logical processing order, why this query fails and how to fix it.
PROBLEM 2BASIC
Given the query SELECT region, COUNT(*) AS num_orders FROM orders WHERE status = 'shipped' GROUP BY region ORDER BY num_orders DESC LIMIT 3;, list the logical processing order of the clauses as they are evaluated by the engine. For each phase, state what the virtual table contains after that phase completes.
PROBLEM 3INTERMEDIATE
Explain why the following query is valid in standard SQL: SELECT customer_id, SUM(amount) FROM orders GROUP BY customer_id ORDER BY MAX(amount) DESC; even though MAX(amount) does not appear in the SELECT list. Relate your answer to the logical order of operations.
PROBLEM 4APPLIED
You are building a dashboard query for an e-commerce platform. You need the top 10 product categories by total revenue for 2024, but only categories with more than 100 orders and revenue exceeding $50,000. A teammate writes: SELECT category, SUM(price) AS revenue, COUNT(*) AS order_count FROM sales WHERE YEAR(sale_date) = 2024 AND order_count > 100 GROUP BY category HAVING revenue > 50000 ORDER BY revenue DESC LIMIT 10; Identify all errors, explain each using the logical order, and provide the corrected query.
PROBLEM 5CRITICAL THINKING
Some SQL dialects (notably MySQL and SQLite) allow column aliases in GROUP BY and HAVING. Analyze the trade-offs of this design decision. Consider portability, potential for ambiguity (what if an alias collides with a column name?), and how this relates to the theoretical logical processing order. Would you recommend relying on this extension in production code?

Summary — SQL Logical Order of Operations

The SQL logical processing order defines a seven-phase pipeline that governs how a query is conceptually evaluated, regardless of syntactic order. Evaluation begins with FROM, which assembles and joins source tables. WHERE then filters individual rows using Boolean predicates—no aggregates allowed. GROUP BY partitions the surviving rows into groups, and HAVING filters those groups using aggregate conditions. SELECT evaluates expressions and defines column aliases. ORDER BY sorts the result, and LIMIT truncates it.

The most important practical consequence is scope visibility: column aliases defined in SELECT are invisible to WHERE, GROUP BY, and HAVING because those phases execute earlier. Aggregate functions cannot appear in WHERE because groups have not yet been formed. Placing row-level filters in HAVING instead of WHERE is both semantically incorrect and performance-degrading. The logical order is not just a theoretical curiosity—it is the single mental model that explains alias errors, aggregate placement rules, and the relationship between physical execution plans and query correctness. Master this pipeline, and every SQL query you write will be predictable and portable.

Varsity Tutors • SQL • SQL Logical Order of Operations