One SQL statement fails with a message about a misspelled keyword; another parses fine but fails saying a referenced column does not exist. Explain why these two failures are detected at different points in query processing, and what the database knows at each point.
answer
- parser = grammar only, no schema
- binder = catalog: names, types, overloads, privileges
- syntax error points at text position
- semantic error names a missing/ambiguous object
- runtime errors (divide by zero, constraint) are a third bucket
basics
~20 sThe parser only checks the statement against the SQL grammar, so a misspelled keyword fails there. Names and types are checked later, during binding against the system catalog, so an unknown column fails only after the statement parses successfully.
solid answer
~60 sTwo different kinds of knowledge are consulted at two different times. **Parsing** matches the token stream against the SQL grammar. It knows keywords, punctuation and literal forms - nothing about your database. A misspelled keyword or an unbalanced parenthesis is a *syntax* error and is reported here, usually with a character position, before any catalog is touched. **Binding (semantic analysis)** happens after a successful parse. It walks the syntax tree and resolves each identifier against the **system catalog**: does this table exist in the schemas the session searches, does this column exist on it, what is its type, is this function overload real, does the user have privileges? Failures here are *semantic* errors: unknown table or column, ambiguous reference, incompatible types. The practical consequences: a statement referring to a table you will create tomorrow parses today but cannot bind; error messages differ in shape (position in text versus name of an object); and a statement can be syntactically perfect and still be rejected by a database that has a different schema than the one you developed against.
go deeper
State the split clearly: grammar first with no schema knowledge, then name and type resolution against the catalog. One example of each error is enough.
Add what binding actually resolves - schema search order, column ambiguity, function overloads, implicit casts, privileges - and note that SELECT * is expanded here.
Use the split operationally: match error shapes to root causes (missing migration, wrong schema search order, wrong database) and mention prepare-time failure as an early detection point.
Frame it as a contract boundary: text-only failures are catchable in CI without a database, catalog-dependent failures need an environment with the right schema, which shapes how you gate migrations and validate SQL in a pipeline.
## The two questions a database asks about your statement Before a statement can run, the database must answer two independent questions: 1. **Is this legal SQL?** - a question about the *text*, answered by the grammar. 2. **Does this refer to real things, consistently typed?** - a question about *this database*, answered by the catalog. These are answered at two different stages, and that ordering explains every error message you have ever seen from a database front-end. ## Stage one: the parser and syntax errors The parser first lexes the statement into tokens: keywords, identifiers, string and numeric literals, operators, punctuation. Then it checks whether that sequence of tokens matches the SQL grammar. Its universe is the grammar plus the character stream. It does not open a single table. Typical failures here: - a misspelled keyword, which the lexer cannot classify in a legal position; - a missing or extra comma in a projection list; - an unbalanced parenthesis or an unterminated string literal; - clauses in an illegal order. Because the parser reasons about text positions, the error usually reports a line and column or points at the offending token. A key implication: if the statement does not parse, **nothing else has happened**. No catalog lookup, no privilege check, no plan. That is why a statement full of nonexistent tables will still report only the syntax problem: parsing halts before anything else runs. One subtlety worth mentioning: whether an identifier is *legal* is a syntax question (quoting rules, allowed characters, reserved words), but whether it *exists* is not. ## Stage two: the binder and semantic errors Once a syntax tree exists, the binder walks it and consults the system catalog - the database's internal description of itself. For every identifier it must find exactly one referent: - **Table names.** An unqualified name is resolved using the session's schema search order. Failure to find one gives "relation/table does not exist"; finding it in an unexpected schema is worse, because it succeeds against the wrong object. - **Column names.** Each column reference must resolve to exactly one column of exactly one relation in scope. Zero matches gives "column does not exist"; more than one gives "ambiguous column reference" - a semantic error even though the text is flawless. - **Types.** Every expression gets a type. Comparing a text column to a number either resolves through an implicit cast or fails as a type error, and this is decided here, not at runtime. - **Functions and operators.** A call resolves to one specific overload based on argument types. "Function does not exist" often really means "no overload matches these argument types". - **Privileges.** Most engines check access rights during or immediately after binding, so "permission denied" is a semantic-stage failure too. The binder also fixes things that look dynamic but are not: `*` is expanded into a concrete column list here, so the result shape is decided at bind time. ## Why the ordering matters in practice - **Reading errors.** A position-based message means you have a typing problem in the text. An object-named message means the text is fine and your *environment* is wrong - wrong database, wrong schema search order, migration not applied, view not created. - **Deploying migrations.** A statement referencing a table that a later migration creates will parse anywhere but bind only after that migration runs. This is why "it works on my machine" bugs so often produce semantic, never syntax, errors. - **Prepared statements.** Preparing a statement performs parse and bind, so a prepare fails immediately on unknown objects - a useful early check - while values supplied later cannot cause either class of error. - **Dynamic SQL.** Building statements from strings pushes both failure classes to runtime; a fixed statement with bind parameters can only fail semantically once, at prepare time. - **Runtime errors are a third category.** Division by zero, a unique-constraint violation, a cast failure on an actual value, or an arithmetic overflow are neither syntax nor semantic errors - they happen during execution, when real values arrive, and they are data-dependent, so the same statement may succeed for one row set and fail for another. ## Putting it together Syntax = grammar, no schema, fails at parse, points at text. Semantics = catalog, names and types and privileges, fails at bind, names an object. Data-dependent failures = execution, points at a value. Being able to sort an error into one of these three buckets is the fastest first step in debugging any database failure.
- Which stage reports an 'ambiguous column reference' when two joined tables both have a column of that name, and why is that not a syntax error?The binder reports it, during semantic analysis. The text is perfectly legal SQL, so the grammar has nothing to object to; the problem only appears once the resolver looks at the catalog and finds two candidate columns in scope with no way to choose. Ambiguity is a fact about this database's schema, not about the statement text.
- Where does a unique-constraint violation fall in this classification?Neither - it is a runtime failure during execution, raised when an actual value collides with existing data. The statement parsed and bound successfully, and the same statement will succeed on a different data set, which is exactly what distinguishes data-dependent errors from syntax and semantic errors.
saying these in an interview costs you the question
- Saying the parser checks whether tables exist.
- Calling 'column does not exist' a syntax error.
- Assuming a statement that parses is guaranteed to run.
- Treating a constraint violation as a semantic/binding error rather than a runtime one.
- Thinking SELECT * is resolved at execution time rather than during binding.