Generated Columns and Invisible Columns
Generated columns make expressions indexable (JSON paths, normalized strings, derived dates) and guarantee consistent derived values.
Concept
Generated columns (5.7+) compute their value from an expression on other columns of the same row. VIRTUAL columns are computed on read (no storage); STORED columns are computed on write and stored. Both can be indexed (InnoDB supports secondary indexes on virtual columns).
Invisible columns (8.0.23+) are hidden from SELECT * and from INSERT without a column list, but can be referenced explicitly — useful for adding columns without breaking legacy code.
Why It Matters
Generated columns make expressions indexable (JSON paths, normalized strings, derived dates) and guarantee consistent derived values. Invisible columns allow safe schema evolution.
Syntax
col_name type GENERATED ALWAYS AS (expr) [VIRTUAL | STORED] [NOT NULL] [UNIQUE],
col_name type INVISIBLE,
ALTER TABLE t ALTER COLUMN c SET VISIBLE | SET INVISIBLE;Example
Rules: expressions must be deterministic (no NOW(), RAND(), subqueries, or stored functions) and may reference only columns of the same row — any non-generated column, or a generated column defined earlier in the table.
VIRTUAL vs STORED:
On INSERT/UPDATE Yes (secondary) Yes (including PK) Instant (usually) Cheap expressions, index targets Expensive expressions read often Invisible column:
CREATE TABLE sensor_readings (
reading_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
vin CHAR(17) NOT NULL,
captured_at DATETIME(3) NOT NULL,
temp_c DECIMAL(5,1) NOT NULL,
temp_f DECIMAL(5,1) GENERATED ALWAYS AS (temp_c * 9 / 5 + 32) VIRTUAL,
capture_day DATE GENERATED ALWAYS AS (DATE(captured_at)) STORED,
wmi CHAR(3) GENERATED ALWAYS AS (LEFT(vin, 3)) VIRTUAL, -- World Manufacturer Identifier
INDEX ix_readings_day (capture_day),
INDEX ix_readings_wmi (wmi)
);
INSERT INTO sensor_readings (vin, captured_at, temp_c) VALUES ('EHBAUREV250000001', '2026-03-10 10:01:00.000', 92.0);
SELECT temp_c, temp_f, capture_day, wmi FROM sensor_readings;
-- 92.0 | 197.6 | 2026-03-10 | EHB
INSERT INTO sensor_readings (vin, captured_at, temp_c, temp_f) VALUES ('X', NOW(), 1, 2);
-- ERROR 3105: The value specified for generated column 'temp_f' in table 'sensor_readings' is not allowed.ALTER TABLE customers ADD COLUMN loyalty_tier VARCHAR(10) INVISIBLE DEFAULT 'BASIC';
SELECT * FROM customers LIMIT 1; -- loyalty_tier not shown
SELECT customer_id, loyalty_tier FROM customers LIMIT 1; -- shown when namedExpected Result
EXPLAIN SELECT COUNT(*) FROM sensor_readings WHERE wmi = 'EHB';
-- key: ix_readings_wmi (the virtual column's index is used)Common Mistakes
Non-deterministic expressions (rejected).
Inserting explicit values into generated columns.
Expecting SELECT * to show invisible columns.
Best Practices
Use VIRTUAL + index for JSON paths and derived lookup keys.
Keep expressions simple and documented.
Interview Relevance
Low-medium: "VIRTUAL vs STORED generated columns?", "How do you index a JSON field?".
Practice Questions
Add a generated column model_year_code = the 10th character of the VIN and index it.
Add a STORED column for order month and group by it.