Walk through what a relational database does with a SQL statement from the moment the text arrives at the server until rows come back to the client. Name each stage and say what it produces.
answer
- text -> AST -> logical plan -> physical plan -> rows
- parser knows grammar, not schema
- binder consults the catalog: names, types, privileges
- optimizer consults statistics, not text
- executor pulls rows through the operator tree
basics
~20 sParser checks grammar and builds a syntax tree. Binder resolves names and types against the catalog, producing a logical plan. Optimizer rewrites it and picks algorithms using statistics, producing a physical plan. Executor runs that plan tree and returns rows.
solid answer
~60 sEach stage has a concrete output: 1. **Parse** - lex and parse the text against the SQL grammar. Output: an abstract syntax tree. The parser knows nothing about your schema, so only *syntax* errors appear here. 2. **Bind / semantic analysis** - walk the tree and resolve every identifier against the system catalog: table names to object IDs, columns to ordinals and data types, functions to a specific overload. Type-check expressions, expand `*`, inline views, check privileges. Output: a validated **logical plan** - relational algebra describing *what* result is wanted. 3. **Rewrite / normalize** - semantics-preserving transformations of that logical plan. 4. **Optimize** - enumerate physical alternatives, cost them with catalog statistics, choose access methods, join algorithms and join order. Output: a **physical plan**, a tree of concrete operators. 5. **Execute** - the executor walks the operator tree, pulls rows through it under the transaction's visibility rules, and streams the result back. The punchline: the statement text determines stages 1-2; the *statistics* determine stage 4. That is why the same text can suddenly run differently.
code
text · 8 linesSQL text: SELECT c.name FROM customers c WHERE c.id = 42
Logical plan: Project[c.name]
Filter[c.id = 42]
Relation[customers AS c]
Physical plan: Project(cost=8.3 rows=1)
IndexLookup(customers_pk, key = 42) (cost=8.3 rows=1)go deeper
Name the stages in order and state one output each: syntax tree, resolved/logical plan, physical plan, rows. Being able to say the parser does not know your schema already puts you ahead.
Explain what the binder pulls from the catalog (object IDs, types, overloads, privileges) and that the optimizer is cost-based on statistics, so text alone does not determine the plan.
Use the stages as a diagnostic frame: map a real symptom (invalid identifier, plan regression after a load, spill to disk) to the stage that produced it, and mention prepared statements reusing earlier stages.
Discuss where the boundaries constrain platform design - what can be cached and keyed at each stage, how catalog versioning drives invalidation, and the cost of statistics quality as a system-wide reliability input.
## Why this question is asked This pipeline is the map that every other query-performance conversation hangs off. If you know which stage produced a given behaviour, you know where to look: a syntax error is a text problem, a "column does not exist" is a catalog problem, a bad join order is an optimizer problem, and a slow scan that matches the plan is an execution problem. Interviewers use it as a structuring question - they want to hear ordered stages, each with an input and an output, not a vague "it parses and runs it". ## Stage 1: parsing (syntax only) The server receives a string. A lexer splits it into tokens (keywords, identifiers, literals, operators), and a parser matches that token stream against the SQL grammar. The output is an **abstract syntax tree (AST)**: a tree whose nodes mirror the written statement - a select node with a projection list, a from list, a where expression, and so on. The critical property is that the parser has **no knowledge of your schema**. It does not know whether a table exists or whether a column is an integer. It only knows whether the words are arranged legally. A misspelled keyword, an unbalanced parenthesis, or a missing comma dies here, and the error message typically points at a character position in the text. ## Stage 2: semantic analysis / binding The binder (also called the analyser or resolver) walks the AST and connects it to reality by consulting the **system catalog** - the database's own tables that describe tables, columns, types, functions and privileges. For each identifier it answers: which object is this? Concretely it: - resolves an unqualified table name using the session's schema search order, and records the resolved object identifier; - resolves each column reference to a specific relation and column, and attaches that column's **data type**; - expands `*` into the actual column list, so the result shape is fixed now, not at execution time; - resolves function and operator calls to one specific overload, inserting implicit casts where the type rules allow; - inlines views by substituting their stored definitions; - checks that the user is permitted to touch the objects involved. The output is a fully annotated, type-checked tree - effectively a **logical plan**: an algebraic description (scan, filter, join, project, aggregate, sort) of what result is wanted, with no decision yet about *how*. Everything the parser could not know surfaces here: unknown table or column, ambiguous column reference, wrong argument types, insufficient privileges. ## Stage 3: rewrite / normalization Before costing anything, engines normalize the logical plan with transformations that are guaranteed to preserve meaning - folding constants, flattening nested structures, normalizing predicates into a canonical form. This is still a *logical* plan: it says what, not how. (The specific rewrite rules are a topic of their own.) ## Stage 4: optimization / planning The optimizer enumerates physically executable alternatives for the same logical plan and picks one. For a two-table join it might consider an index lookup driving a nested loop, or two scans feeding a hash join; for a three-table join it also chooses the order. Each candidate is costed using **catalog statistics** - row counts, distinct value counts, histograms, correlation - plus a cost model that weights I/O and CPU. The cheapest estimated candidate wins. The output is the **physical plan**: a tree of concrete operators, each with a chosen algorithm and its estimated row count and cost. Two runs of identical text can produce different physical plans if statistics, indexes, or the amount of available memory changed. That is the single most important consequence of this stage boundary. ## Stage 5: execution The executor takes the physical plan tree and runs it. The classic model is demand-driven: the top operator asks its child for the next row, which asks its own child, and rows are pulled up through the tree. Along the way the executor applies the transaction's visibility rules (which row versions this statement is allowed to see), acquires the locks or latches it needs, spills to disk if a sort or hash exceeds its memory budget, and pushes result rows back to the client - often incrementally, so a `LIMIT`-style query can stop early. ## What the stage boundaries buy you - **Diagnosis.** "Invalid identifier" means you never reached the optimizer. "The plan looks sensible but rows returned are 1000x the estimate" means binding and planning were fine and your statistics are wrong. - **Prepared statements.** Parsing and binding depend only on text plus catalog, so their results can be cached and reused; planning may or may not be reused, which is a separate design decision an engine makes. - **Version dependence.** Because the plan is chosen from statistics, a query that was fast for months can regress after a bulk load, with no code change at all. ## What interviewers listen for Named stages in order, one concrete artefact per stage (AST, logical plan, physical plan, rows), and the awareness that syntax, semantics and cost are three different kinds of knowledge consulted at three different times.
- Which stage decides whether an index is used, and why can that decision change without the query changing?The optimizer, in the planning stage. It compares costed alternatives using catalog statistics, so if row counts, value distributions, available indexes, or memory settings change, the cheapest candidate can change too. The statement text is identical but the inputs to the cost model are not, which is why queries regress after bulk loads or statistics refreshes.
- If SELECT * is expanded during binding, what happens to an already-bound statement when someone adds a column to the table?The bound statement holds the old column list, so it is stale and must be re-bound; engines detect this through catalog version tracking and invalidate the cached statement. This is exactly why SELECT * in application code is fragile: the result shape is fixed at bind time, and a schema change silently changes it on the next bind.
Like compiling code: the parser checks the grammar, the semantic pass resolves symbols against declarations, the optimizer picks machine-level strategies, and only then does anything run.
saying these in an interview costs you the question
- Saying the parser checks whether tables and columns exist - it only checks grammar.
- Merging binding and optimization into one 'the database figures it out' step.
- Claiming the optimizer reads the actual data to decide; it reads summary statistics.
- Believing identical SQL text always yields the identical execution plan.
- Thinking the executor re-decides join algorithms at runtime; the algorithm is fixed in the physical plan.