✏️ Explanatory Question

Generated columns — STORED vs VIRTUAL and indexing computed expressions

👁 8 Views
📘 Detailed Answer
🟢 Easy
💡

Answer with Explanation

Section 21: Generated Columns

127

Generated columns — STORED vs VIRTUAL and indexing computed expressions

Level: Hard — A modern MySQL 8.0 feature that solves the "function on a column kills the index" problem elegantly.

Scenario: Users search by full name, but you store first_name and last_name separately. Your query WHERE CONCAT(first_name,' ',last_name) = 'Rumman Ansari' cannot use an index (function on columns) and does a full scan. How do you make this computed value indexable and always consistent?

What Generated Columns Are (MySQL 5.7+)

A generated column is a column whose value is automatically computed from an expression over other columns — you never insert into it directly. It keeps a derived value always in sync with its source columns, and (crucially) you can index it.

STORED vs VIRTUAL

  • VIRTUAL (default): Computed on read, not stored on disk. No storage cost, instant to add, but computed each time it is accessed.
  • STORED: Computed on write and physically stored. Uses disk space, slower writes, but no compute on read.

STORED vs VIRTUAL Comparison

AspectVIRTUALSTORED
Disk storageNoneUses space
ComputedOn readOn write
Write costLowerHigher
Can be indexedYesYes
Add via ALTERInstant (metadata)Rebuilds table
Best forRead-light, indexed lookupsRead-heavy, complex expressions

Key insight: Even a VIRTUAL column can be indexed — when you add an index on it, the index physically stores the computed value, so lookups are fast without persisting the column in the row. That is why VIRTUAL + index is usually the best default.

The Fix — Indexable Full-Name Column

-- Add a VIRTUAL generated column and index it
ALTER TABLE users
  ADD COLUMN full_name VARCHAR(200)
      AS (CONCAT(first_name, ' ', last_name)) VIRTUAL,
  ADD INDEX idx_full_name (full_name);

-- Now this uses the index (no full scan, no function on raw columns)
SELECT * FROM users WHERE full_name = 'Rumman Ansari';

-- Verify
EXPLAIN SELECT * FROM users WHERE full_name = 'Rumman Ansari';

Defining Generated Columns at Create Time

CREATE TABLE orders (
    id        BIGINT AUTO_INCREMENT PRIMARY KEY,
    quantity  INT,
    price     DECIMAL(10,2),
    -- STORED: total is persisted, good if read very often
    total     DECIMAL(12,2) AS (quantity * price) STORED,
    created_at DATETIME,
    -- VIRTUAL: derive a date for indexing/date-only searches
    order_date DATE AS (DATE(created_at)) VIRTUAL,
    INDEX idx_order_date (order_date)
);

-- You never insert into generated columns; they compute automatically
INSERT INTO orders (quantity, price, created_at)
VALUES (3, 100.00, '2026-08-02 14:30:00');
-- total = 300.00, order_date = 2026-08-02 (both auto-computed)

Great Use Cases

  • Indexing a JSON path (attributes->>'$.color').
  • Making function-based searches sargable (DATE(created_at), UPPER(email)).
  • Precomputing derived values (line totals, tax, full names).
  • Enforcing computed constraints (a CHECK on a generated column).

Restrictions to remember: The expression must be deterministic (no NOW(), RAND(), user variables, or subqueries). You cannot write to a generated column directly. And a STORED generated column change via ALTER rebuilds the table, whereas adding a VIRTUAL one is a fast metadata-only change.

Interviewer follow-up: "When would you pick STORED over VIRTUAL?" → Choose STORED when the expression is expensive and the column is read far more than written (compute once on write, not every read), or when a downstream feature requires it to be materialized. Choose VIRTUAL for cheap expressions or when you mainly need it indexed — the index already stores the value, so persisting the column too would just waste space.