powershelldba.de · Uwe Janke

Persisted vs. Non-Persisted Computed Columns in SQL Server

A computed column looks like a free convenience: define an expression once, reference it everywhere. Whether it actually is free depends entirely on one keyword: PERSISTED. Get that choice wrong on a small lookup table and nothing happens. Get it wrong on a table with hundreds of millions of rows, and you've either doubled your I/O on every read, or scheduled a full-table rewrite you didn't know you were signing up for.

What a Computed Column Actually Is

A computed column doesn't store a value you assign; it stores (or re-derives) the result of an expression over other columns in the same row. By default, it stores nothing at all.

CREATE TABLE dbo.Customers (
  CustomerID   INT           NOT NULL PRIMARY KEY,
  FirstName    NVARCHAR(50)  NOT NULL,
  LastName     NVARCHAR(50)  NOT NULL,
  FullName     AS (FirstName + N' ' + LastName)
);

-- FullName is NOT stored anywhere on disk.
-- Every SELECT that touches it re-runs the concatenation.
SELECT CustomerID, FullName FROM dbo.Customers WHERE CustomerID = 42;

That's a non-persisted computed column, the default. Add PERSISTED and SQL Server physically stores the computed value in the row, right alongside the real columns, and recalculates it only when a source column changes:

CREATE TABLE dbo.Customers (
  CustomerID   INT           NOT NULL PRIMARY KEY,
  FirstName    NVARCHAR(50)  NOT NULL,
  LastName     NVARCHAR(50)  NOT NULL,
  FullName     AS (FirstName + N' ' + LastName) PERSISTED
);

Same query, same result. Completely different cost profile, and it moves in opposite directions depending on whether you're reading or writing.

Persisted vs. Non-Persisted: What Actually Changes

Aspect Non-Persisted (default) PERSISTED
On-disk storage None; purely virtual Stored in the row like a real column
Cost on SELECT Recomputed every time the column is read Read directly, no recomputation
Cost on INSERT/UPDATE None: the expression is never touched at write time Recomputed and written whenever a source column changes
Can be indexed Only if deterministic and precise Deterministic and precise: yes. Deterministic but imprecise: stored, but still not indexable
Can be a PRIMARY KEY / UNIQUE constraint No Yes, if deterministic and precise
Row size impact None Grows every row by the size of the result
⚠ You can't write to it either way. Computed columns, persisted or not, are never a valid target for INSERT or UPDATE. SQL Server derives the value; you don't supply one. Trying to raises a compile error.

When to Use PERSISTED

Persist the value when the cost of computing it is higher than the cost of storing it, or when SQL Server structurally requires storage to do something else with the column:

When NOT to Use PERSISTED

What This Means for INSERT and UPDATE

With a non-persisted computed column, the write path is completely unaware it exists. INSERT and UPDATE statements against the base columns run exactly as fast as they would without the computed column, because nothing is evaluated until a later SELECT asks for it.

With PERSISTED, every write that changes a source column becomes slightly heavier:

-- FullName is PERSISTED, derived from FirstName + LastName
UPDATE dbo.Customers
SET LastName = N'Jankowski'
WHERE CustomerID = 42;

-- What actually happens under PERSISTED:
-- 1. LastName is updated as normal
-- 2. FullName is re-evaluated
-- 3. The new FullName value is written into the row
-- 4. Both changes are logged
-- 5. If FullName is indexed, the index is updated too

-- Under non-persisted, steps 2–5 simply don't happen.
-- FullName is recomputed the next time someone SELECTs it.

None of this is dramatic for a single-row UPDATE. It adds up when the same pattern runs at OLTP volume, or inside a bulk load: a persisted computed column with an expensive expression, sitting on a table that ingests millions of rows a day, turns "just a derived column" into a measurable slice of your total write cost.

Very Large Tables: Where This Gets Expensive

The write-time cost above is per-row and ongoing. There's a separate, one-time cost that's easy to underestimate: adding a persisted computed column to a table that already has data.

-- Table already has 400 million rows
ALTER TABLE dbo.Orders
ADD OrderTotal AS (Quantity * UnitPrice - Discount) PERSISTED;

-- ✗ SQL Server must evaluate the expression for every EXISTING row
-- ✗ Every row is physically rewritten to include the new value
-- ✗ This happens inside ONE transaction, fully logged
-- ✗ SCH-M lock on the table for the entire duration: no reads, no writes
-- ✗ Same profile as ALTER COLUMN on a huge table: log growth,
--   long blocking, and a rollback that's as expensive as the operation itself

This is the same size-of-data problem covered in ALTER COLUMN on Large Tables: SQL Server doesn't distinguish "add a persisted computed column" from "rewrite every row." The same batching and log-management strategies from that article apply here, because under the hood it's the same kind of operation.

The one case that stays cheap: adding the same column without PERSISTED is metadata-only, regardless of table size, because there's nothing to compute or store for the existing rows. If you don't need the column indexed, don't need it as a key, and the expression is cheap to evaluate on read, skip PERSISTED and the ALTER completes instantly.

Even once it's in place, a persisted column keeps costing you on a huge table in ways that don't show up in any single query plan: every row is wider, so fewer rows fit per 8 KB page, so full scans and range scans touch more pages, the buffer pool holds less useful data per gigabyte of RAM, and backups take longer simply because there's more to write out. On a few thousand rows none of this registers. On a table in the hundreds of millions, a persisted column that turned out to be unnecessary is a permanent, silent overhead.

The Limits of Computed Columns

Whether an expression can be persisted, and separately, whether it can be indexed, comes down to two properties SQL Server evaluates for every computed column: deterministic (same inputs always produce the same output) and precise (no floating-point rounding involved).

Expression type Example PERSISTED? Indexable?
Non-deterministic GETDATE(), NEWID(), RAND() No No
Deterministic, imprecise FLOAT/REAL arithmetic Yes No
Deterministic, precise Integer/DECIMAL arithmetic, string concatenation Yes Yes

A non-deterministic expression is stuck as non-persisted forever, no matter how expensive it is to evaluate; you can't index around it either. A few other limits round out the list:

Checking What You Already Have

Before assuming a computed column somewhere is "probably persisted," check. It's a common surprise on inherited schemas:

SELECT
  t.name              AS TableName,
  c.name              AS ColumnName,
  cc.definition,
  cc.is_persisted,
  cc.is_deterministic
FROM sys.computed_columns cc
JOIN sys.tables t ON t.object_id = cc.object_id
JOIN sys.columns c ON c.object_id = cc.object_id AND c.column_id = cc.column_id
ORDER BY t.name, c.name;

Best Practices Checklist

The Bottom Line

A computed column's real cost doesn't disappear based on whether you write PERSISTED; it just moves. Non-persisted defers the cost to every future read. Persisted pays it once per write and stores the result, at the price of a wider row and, on an existing large table, a full rewrite to get there. Choose based on your actual read-to-write ratio and the cost of the expression itself, not on which option feels more "correct." On a lookup table with a few thousand rows, either choice is invisible. On a table with hundreds of millions, it's a decision that shows up in your storage bill and your backup window for as long as the column exists.

← Back to Blog