{"post":{"slug":"generated-columns-explained-2026","title":"Generated Columns: Let the Database Compute It","excerpt":"A value derived from other columns can be maintained by the database itself, indexed like any other column, and never fall out of sync with its inputs.","category":"code","date":"August 15, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">A</span> table stores `first_name` and `last_name`, and half the application needs a full name. So a `full_name` column gets added, and application code sets it on insert. Then a second code path forgets, and a data migration script does not know about it, and someone updates a surname directly in a console during an incident.\n\nNow `full_name` disagrees with its inputs on an unknown number of rows, and nothing will tell you which.\n\nA generated column removes the possibility. The database computes it from an expression you define, on every write, from every source. There is no code path that can skip it.\n\n## What changed in 2026\n\n- **Support became broad and consistent.** Generated columns moved from a database-specific feature to something available across mainstream engines with similar semantics.\n- **JSON extraction became the common use.** Pulling a field out of a JSON column into an indexable generated column turned into a standard pattern for semi-structured data.\n- **Stored versus virtual diverged by engine.** Which variants each database supports remained inconsistent, so portability requires checking.\n- **Search normalisation adopted them.** Precomputing lowercase or accent-stripped forms for searching became a routine use.\n\n## Stored and virtual\n\nTwo variants, and which you have depends on your database.\n\n**Stored** columns are computed on write and physically saved. They occupy disk space, they are read like any other column, and — critically — **they can be indexed**.\n\n**Virtual** columns are computed on read and stored nowhere. No disk cost, computed every time, and generally not indexable.\n\n| | Stored | Virtual |\n| --- | --- | --- |\n| Disk usage | Yes | None |\n| Write cost | Computed on every write | None |\n| Read cost | None | Computed per read |\n| Indexable | Yes | Usually not |\n| Availability | Broad | Varies by engine |\n\nFor most practical purposes, stored is what you want, and the reason is the index row. An indexable derived value is what makes generated columns worth adding rather than just using a view.\n\n## Where they earn their place\n\n**Indexing an expression.** Searching case-insensitively means either an expression index or a generated column holding the lowercase form. The generated column is often clearer, and it can be referenced in queries directly rather than requiring the query to repeat the expression exactly for [the planner to match it](/blog/query-planner-explained-2026).\n\n**Extracting from JSON.** A JSON column is flexible and awkward to index and filter well. A generated column pulling out the field you actually query gives you a normal, indexable, typed column while keeping the flexible document intact. This is the most common modern use.\n\n**Enforcing derived consistency.** Anything computed from other columns — a total from a quantity and a price, a duration from two timestamps, a normalised identifier — stays correct by construction.\n\n**Search normalisation.** Precomputing a stripped, lowercased, whitespace-collapsed form of a text field, then indexing that, makes fuzzy matching straightforward without repeating normalisation in every query.\n\n## The constraints\n\n**The expression must be deterministic.** The same inputs must always produce the same output. That rules out the current time, random values, and anything depending on session settings. A column computing \"days since created\" cannot be generated, because its value changes without the row changing.\n\n**It can only reference the same row.** No subqueries, no other tables, no aggregates. A generated column cannot hold a count of related rows — that is what a [materialised view](/blog/what-is-a-materialized-view-2026) is for.\n\n**You cannot write to it.** An insert or update naming a generated column is an error. This surprises ORMs that generate `INSERT` statements listing every column, and it is usually fixed with an annotation telling the ORM the column is read-only.\n\n**Changing the expression means rewriting the column.** On a large table that is a migration with the same care as any other, and worth planning per [expand and contract](/blog/expand-contract-migrations-2026).\n\n## Common mistakes\n\n- **Trying to use non-deterministic expressions.** Rejected, and the reason is not always obvious from the error.\n- **Expecting to reference other tables.** Not possible; use a view or materialised view.\n- **ORM insert failures.** Mark the column read-only in your model.\n- **Generating something never filtered or sorted on.** A view costs nothing and stays flexible.\n- **Assuming virtual columns are indexable.** Usually they are not, and this is engine-specific.\n- **Forgetting the write cost.** A stored column is computed on every write; an expensive expression on a hot table is a real cost.\n\n## FAQ\n\n**Generated column or expression index?**\nSimilar effect for indexing purposes. A generated column is more discoverable — it appears in the schema and can be selected and referenced by name — while an expression index is invisible unless you look for it and requires queries to match the expression exactly. Prefer the generated column when the value is meaningful to read.\n\n**Generated column or view?**\nA view is better when the value is only ever read and never filtered on, since it costs nothing and can be changed freely. A generated column is better when you need to index it.\n\n**Can I add one to a large existing table?**\nAdding a stored generated column requires computing it for every row, which rewrites the table and takes a lock in most engines. Treat it as a heavyweight migration on a large table.\n\n**Do they work with partitioning?**\nGenerally yes, and support for using one as a partition key varies. Check your engine before designing around it — see [table partitioning](/blog/table-partitioning-explained-2026).\n\n## Where to go next\n\nFor indexing the values you generate, read [database indexing explained](/blog/database-indexing-explained-2026) and [partial indexes](/blog/partial-indexes-explained-2026). For derived values spanning multiple tables, [what is a materialized view](/blog/what-is-a-materialized-view-2026).","hue":304,"metaTitle":"Generated Columns: Let the Database Compute It","metaDescription":"How generated columns keep derived values consistent automatically, why stored columns can be indexed, and the determinism rules that constrain them.","keywords":["generated columns","computed column database","stored vs virtual column","derived value consistency","expression index alternative","postgres generated column","denormalization pattern","database design 2026"],"tldr":["**The database keeps it correct, not your application** — every writer gets the right value, including scripts and manual updates.","**Stored columns can be indexed like ordinary ones** — which is the main reason to reach for them.","**The expression must be deterministic** — no current time, no random values, no references to other tables.","**You cannot write to them** — an insert that names a generated column is an error, which surprises ORMs.","**Skip:** a generated column for something you never filter or sort on. A view computes it just as well for free."],"readTime":"4 min read","wordCount":949},"related":[{"slug":"json-columns-explained-2026","title":"JSON Columns: Flexible Schema With Real Tradeoffs","excerpt":"Storing a document in a column avoids schema migrations for every new field. It also gives up type checking, constraint enforcement, and straightforward indexing.","category":"code","date":"January 5, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":305},{"slug":"database-design-patterns-2026","title":"Database design patterns in 2026: practical patterns that scale","excerpt":"The database patterns that survive contact with real traffic are boring and well-known. Here is the 2026 practical guide — indexes, denormalization, soft deletes, audit trails, and the trade-offs.","category":"code","date":"February 13, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":152},{"slug":"postgres-18-features-2026","title":"Postgres 18 Features: Async I/O and What It Means","excerpt":"Postgres 18 added asynchronous I/O, native UUIDv7, virtual generated columns, and OAuth authentication. Here is which changes affect real workloads.","category":"code","date":"August 12, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":205}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"crypto-etf-options-explained-2026","title":"Crypto ETF Options Explained: Income and Real Risk","excerpt":"Listed options on crypto ETFs opened covered call and hedging strategies to ordinary brokerage accounts. Here is how they behave differently from equity options.","category":"finance","date":"August 15, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":300},"older":{"slug":"glass-substrate-packaging-explained-2026","title":"Glass Substrate Packaging: The Next Chip Bottleneck","excerpt":"Chips stopped getting faster mainly by shrinking transistors and started getting faster by packaging better. Here is why glass substrates matter and what they unlock.","category":"tech","date":"August 15, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":252}}