fivenines
4/25

Build Your Own PostgreSQL

SQL as a Language of Relations

Time
6 min
Prerequisites
The Wire Protocol And Session Lifecycle

What You Will Learn

  • Understand the table that does not exist yet in a PostgreSQL-like database
  • Understand from loops to relations in a PostgreSQL-like database
  • Understand relational shape in a PostgreSQL-like database
  • Understand where the abstraction helps in a PostgreSQL-like database
sqllanguagerelationstabledoesexistyetloopsrelationalshape

The table that does not exist yet

SQL is often introduced as a convenient way to get rows out of tables. That description is practical but too small. SQL is powerful because it lets users describe a relation they want, even when that relation is not stored as a table. A query result is itself a table-shaped value with rows, columns, names, and types. Once that idea is clear, select lists, filters, joins, grouping, and ordering stop feeling like disconnected clauses. They are ways of shaping a relation.

A relation is not a file, a screen grid, or a list of objects. It is a set-like collection of tuples with named attributes. Real SQL adds duplicates, nulls, ordering at the edges, and many pragmatic details, but the central idea holds: a query transforms table-shaped inputs into a table-shaped output. The database engine can rearrange many of those transformations because the user describes meaning rather than a step-by-step loop.

From loops to relations

Consider a query that asks for customers with unpaid invoices. One mental model says, "For each customer, look through invoices, keep the ones that match, then print a few fields." That is an algorithm. A relational model says, "Combine customers and invoices where the customer identifiers match, keep rows whose payment status is unpaid, and project the columns needed by the result." The second phrasing gives the optimizer room to choose an index, change join order, or push a predicate earlier.

Projection chooses attributes. Selection filters tuples. A join combines relations by a condition. Aggregation collapses many tuples into grouped summaries. Ordering arranges the final result for presentation. These operations are small enough to understand alone but expressive enough to compose. SQL's surface syntax can be irregular, yet beneath it is a compact language of relational transformations.

Relational shape

Where the abstraction helps

It helps to contrast row-at-a-time thinking with relation-at-a-time thinking. Row-at-a-time thinking mirrors application code. It feels direct because you can imagine loops and conditionals. It also hides opportunities. If the application asks the database for all customers and then loops through invoices itself, it has forced data movement across the network and taken planning choices away from the engine. Relation-at-a-time thinking gives the database the whole shape of the question.

The opposite mistake is to imagine relations as purely abstract and cost-free. A join is a clean logical operation, but physically it can mean nested loops, hashing, sorting, index probes, memory pressure, temporary files, and many pages read from disk. SQL works because the language is separated from execution, not because execution disappears. The system must preserve relational meaning while eventually choosing a concrete physical strategy.

Nulls complicate the clean picture. In SQL, a predicate can be true, false, or unknown. This affects filters, joins, constraints, and indexes. Backend engineers often expect boolean logic to be binary, but SQL's treatment of missing or inapplicable values is part of its contract. A Postgre-like engine must carry types and nullability through analysis and planning because a transformation that is safe for ordinary values can be wrong when unknown enters the expression.

Names also matter. A column reference such as `id` is not meaningful by itself when several relations have an `id`. SQL allows aliases, schemas, implicit search paths, function calls, operators, casts, and star expansion. Before planning can begin, the database must know what every name refers to and what type every expression has. The surface language points toward relations, but it does not yet contain enough information to execute safely.

The written order is a decoy

The order of SQL clauses can also mislead. A select list appears near the front, but conceptually it is often shaped after from, join, where, grouping, and aggregate computation. This mismatch between written order and logical order is one reason SQL parsing alone is insufficient. The database must transform the text into a representation where meaning is explicit, then bind that representation to catalog objects and types.

SQL's main advantage is that it creates a contract between user and engine. The user promises to describe the desired relation using names, predicates, and expressions. The engine promises to find a valid way to produce it under the current transaction, permissions, and data state. That contract is the source of optimization. If the user supplied imperative loops, changing the plan would change the program. Because the user supplies relational meaning, the engine can search for better routes.

From here the database faces a language problem. SQL text arrives as characters, and characters do not yet know about clauses, operators, precedence, or nested subqueries. The next layer must turn a stream of tokens into a tree that preserves the structure of the relational statement.

Into parsing

That tree is the first place where the written question becomes an object the rest of the system can safely transform.

Next step

See what actually stuck.

Take the practice scenarios now.