Chapter 13 · Stored Procedures, Functions, Views, Triggers, Dynamic SQL, and CLR Boundaries

Views, Indexed Views, SCHEMABINDING, Updatability, and Abstraction Costs

Use ordinary and indexed views deliberately, including SCHEMABINDING, updatability, required SET options, physical materialization and write-amplification costs.

Advanced165–210 minutesviews & indexed-view labSQL Server 2025 CU7 · 17.0.4065.4Compatibility 170 · Developer/ExpressSSMS 22.8.2 · Last reviewed August 2026

Learning outcomes

ServiceHub wants to publish a stable reporting surface without giving every analyst knowledge of every base-table column. A developer proposes a view and tells the team that “views cache the query result.” Another developer wants to index every view because “materialized is faster.” Both statements confuse three distinct things: a normal view definition, a schema-bound view, and an indexed view whose first index physically stores rows.

01

Explain ordinary views as stored query definitions and identify when simple views remain updatable.

02

Use SCHEMABINDING to create an explicit dependency boundary and understand its deployment consequences.

03

Create a valid indexed view with the mandatory SET options and unique clustered index prerequisite.

04

Explain automatic indexed-view matching, NOEXPAND and edition/version considerations without assuming universal substitution.

05

Balance read benefit against base-table write amplification, storage, statistics and deployment coupling.

1. A normal view stores a definition, not a result set

sql · create the disposable ServiceHub programmability lab
USE master;GOIF DB_ID(N'ServiceHubProgrammabilityLab') IS NULL  CREATE DATABASE ServiceHubProgrammabilityLab;GOALTER DATABASE ServiceHubProgrammabilityLab SET COMPATIBILITY_LEVEL = 170;GOUSE ServiceHubProgrammabilityLab;GOIF SCHEMA_ID(N'ops') IS NULL EXEC(N'CREATE SCHEMA ops AUTHORIZATION dbo;');IF SCHEMA_ID(N'api') IS NULL EXEC(N'CREATE SCHEMA api AUTHORIZATION dbo;');IF SCHEMA_ID(N'lab13') IS NULL EXEC(N'CREATE SCHEMA lab13 AUTHORIZATION dbo;');GOIF OBJECT_ID(N'ops.WorkOrder',N'U') IS NULLBEGIN  CREATE TABLE ops.WorkOrder  (    work_order_id bigint IDENTITY(1001,1) NOT NULL      CONSTRAINT PK_ops_WorkOrder PRIMARY KEY,    customer_code varchar(16) NOT NULL,    region_code char(3) NOT NULL,    status varchar(16) NOT NULL,    priority tinyint NOT NULL,    opened_at datetime2(0) NOT NULL,    closed_at datetime2(0) NULL,    amount decimal(12,2) NOT NULL,    description nvarchar(400) NULL,    CONSTRAINT CK_ops_WorkOrder_status      CHECK (status IN ('OPEN','ASSIGNED','CLOSED','ESCALATED')),    CONSTRAINT CK_ops_WorkOrder_priority CHECK (priority BETWEEN 1 AND 5)  );  ;WITH n AS  (    SELECT TOP (30000)      ROW_NUMBER() OVER (ORDER BY a.object_id,b.object_id) AS n    FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b  )  INSERT ops.WorkOrder(customer_code,region_code,status,priority,opened_at,closed_at,amount,description)  SELECT CONCAT('CUST-',RIGHT('000000'+CONVERT(varchar(6),n%9000),6)),         CASE n%3 WHEN 0 THEN 'N01' WHEN 1 THEN 'W02' ELSE 'E03' END,         CASE WHEN n%23=0 THEN 'ESCALATED'              WHEN n%5=0 THEN 'CLOSED'              WHEN n%2=0 THEN 'ASSIGNED' ELSE 'OPEN' END,         CONVERT(tinyint,1+n%5),         DATEADD(minute,n,'2026-01-01T00:00:00'),         CASE WHEN n%5=0 THEN DATEADD(minute,n+60,'2026-01-01T00:00:00') END,         CAST(25+(n%25000)/10.0 AS decimal(12,2)),         CONCAT(N'ServiceHub work order ',n)  FROM n;  CREATE INDEX IX_ops_WorkOrder_region_status_opened    ON ops.WorkOrder(region_code,status,opened_at)    INCLUDE(customer_code,priority,amount,closed_at);END;GO
sql · create a stable reporting projection
USE ServiceHubProgrammabilityLab;GOCREATE OR ALTER VIEW api.vOpenWorkOrderASSELECT work_order_id,customer_code,region_code,priority,opened_at,amountFROM ops.WorkOrderWHERE status='OPEN';GOSELECT TOP (5) *FROM api.vOpenWorkOrderORDER BY opened_at DESC,work_order_id DESC;GOSELECT OBJECT_DEFINITION(OBJECT_ID(N'api.vOpenWorkOrder')) AS stored_definition;GO

The view stores the SELECT definition and exposes it as a table-like object. SQL Server normally expands/optimizes the view definition with the caller’s query. There is no independent cache of result rows just because a view exists. Plan caching is separate, and an indexed view is a different object because its unique clustered index stores the materialized result.

Views are useful for naming stable projections, hiding base-table complexity, centralizing some relational rules, and applying permissions at a module boundary. They also introduce coupling: callers may depend on column names/types/order, while nested layers of views can make lineage and tuning harder to see.

2. Updatability depends on whether SQL Server can map the change unambiguously

A simple single-table view can often be updated, and the modification flows to the base table. Aggregation, many joins, DISTINCT and other constructs can make a view non-updatable because there is no unambiguous base-row operation. An INSTEAD OF trigger can implement custom behavior, but then you have explicitly written that translation logic and must test multi-row semantics.

sql · prove a simple view update changes the base table, then roll it back
BEGIN TRAN;DECLARE @id bigint=(SELECT MIN(work_order_id) FROM api.vOpenWorkOrder);SELECT work_order_id,priority FROM ops.WorkOrder WHERE work_order_id=@id;UPDATE api.vOpenWorkOrder SET priority=5 WHERE work_order_id=@id;SELECT work_order_id,priority FROM ops.WorkOrder WHERE work_order_id=@id;ROLLBACK;GO

This transaction demonstrates mapping, not a recommendation to use views as write APIs. If an application’s write contract has validation, authorization and transaction semantics, a procedure is often easier to version and test.

3. SCHEMABINDING creates a deployment boundary

A schema-bound view must use two-part object names and prevents incompatible changes to referenced objects while the dependency exists. This can protect the contract, but it also means deployments must coordinate view changes with table changes instead of relying on accidental breakage.

sql · create a schema-bound view and demonstrate protected dependency
SET ANSI_NULLS ON;SET QUOTED_IDENTIFIER ON;GOCREATE OR ALTER VIEW api.vWorkOrderBoundWITH SCHEMABINDINGASSELECT work_order_id,customer_code,region_code,status,priority,opened_at,amountFROM ops.WorkOrder;GOBEGIN TRY  ALTER TABLE ops.WorkOrder DROP COLUMN description;END TRYBEGIN CATCH  SELECT ERROR_NUMBER() AS expected_error,ERROR_MESSAGE() AS expected_message;END CATCH;GO

The failed ALTER TABLE is evidence that the dependency boundary is enforced. It does not mean schema binding is always desirable; it means you choose coupling deliberately and update the dependent module during deployments.

4. Indexed views: materialization with strict rules

An indexed view begins as a schema-bound deterministic view. The first index must be a unique clustered index; that index physically materializes the view result. SQL Server must maintain that materialized result whenever referenced base data changes. This can make selected read workloads faster and selected write workloads more expensive.

Microsoft requires a fixed set of session options when indexed views are created, maintained, and considered by the optimizer. The critical values include ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL and QUOTED_IDENTIFIER ON, with NUMERIC_ROUNDABORT OFF. The base tables and view must have the same owner, and grouped indexed views require COUNT_BIG(*).

sql · create a small grouped indexed view under the required SET options
SET NUMERIC_ROUNDABORT OFF;SET ANSI_PADDING ON;SET ANSI_WARNINGS ON;SET CONCAT_NULL_YIELDS_NULL ON;SET ARITHABORT ON;SET QUOTED_IDENTIFIER ON;SET ANSI_NULLS ON;GOCREATE OR ALTER VIEW api.vRegionStatusSummaryWITH SCHEMABINDINGASSELECT region_code,status,COUNT_BIG(*) AS order_count,       SUM(CONVERT(bigint,priority)) AS priority_totalFROM ops.WorkOrderGROUP BY region_code,status;GOIF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id=OBJECT_ID(N'api.vRegionStatusSummary') AND name=N'CUX_vRegionStatusSummary')  CREATE UNIQUE CLUSTERED INDEX CUX_vRegionStatusSummary    ON api.vRegionStatusSummary(region_code,status);GOSELECT * FROM api.vRegionStatusSummary WITH (NOEXPAND)ORDER BY region_code,status;GO

NOEXPAND tells SQL Server to treat the indexed view like a table and use its indexed representation when the view is explicitly referenced. Automatic matching—where the optimizer substitutes an indexed view even when the query names base tables—has edition/support considerations, so verify the target edition and actual plan rather than promising substitution.

5. Observe the write cost instead of calling materialization free

sql · inspect the indexed view and operational counters
SELECT i.name,i.type_desc,i.is_unique,p.rowsFROM sys.indexes AS iJOIN sys.partitions AS p ON p.object_id=i.object_id AND p.index_id=i.index_idWHERE i.object_id=OBJECT_ID(N'api.vRegionStatusSummary');GOSELECT index_id,leaf_insert_count,leaf_update_count,leaf_delete_count,       leaf_page_merge_countFROM sys.dm_db_index_operational_stats (DB_ID(),OBJECT_ID(N'api.vRegionStatusSummary'),NULL,NULL);GO

Every qualifying INSERT/UPDATE/DELETE against ops.WorkOrder may also require maintenance of the indexed view. On a write-heavy OLTP workload, that can create CPU, logging, locking and storage costs that outweigh the reporting benefit. Measure representative DML with the indexed view present and removed; do not infer write amplification from the index name alone.

Wrong approach: “index the view because the report is slow”

First inspect the base query, indexes, statistics, cardinality and Query Store evidence. Indexed views have strict semantic/SET-option requirements and maintenance cost. They are a specialized physical design, not the default fix for a complicated view stack.

6. Cleanup and rollback

sql · remove the indexed-view experiment while keeping the ordinary API view
DROP INDEX IF EXISTS CUX_vRegionStatusSummary ON api.vRegionStatusSummary;DROP VIEW IF EXISTS api.vRegionStatusSummary;DROP VIEW IF EXISTS api.vWorkOrderBound;GO

Dropping the unique clustered index removes the materialized indexed representation; dropping the view removes the definition. In production, removal should still follow dependency review and Query Store regression monitoring because callers or plans may depend on the object.

Production judgment

Use ordinary views to publish relational abstractions with stable contracts. Keep the layering shallow enough that operators can trace a slow query back to base tables and predicates. Use schema binding when deployment protection is worth the coupling. Use indexed views only after workload evidence proves a materialized aggregate/projection is valuable enough to pay ongoing maintenance cost and after all required SET/ownership/determinism constraints are operationally enforceable.

The ordinary-view lab works on free Developer/Express. The indexed-view mandatory experiment is designed for free SQL Server 2025 Developer; exact automatic indexed-view matching behavior is edition-sensitive. Compatibility 170 is used for course consistency, but indexed-view fundamentals predate it.

Check your understanding

  1. Does an ordinary view store its result rows?
  2. What is the first index required on an indexed view?
  3. Why can SCHEMABINDING make deployments safer and harder at the same time?
  4. Why must indexed-view SET options be treated as an operational requirement?
  5. What production cost can an indexed view add even if no report is running?
Review the answers

1. No. It stores a query definition; the result is produced when queried unless a physical indexed representation exists.

2. A unique clustered index.

3. It prevents incompatible base-object changes, but deployments must coordinate changes across the dependency boundary.

4. SQL Server requires specific values when creating, maintaining and using indexed views; inconsistent session settings can prevent use or cause DML errors.

5. Every relevant base-table write can require maintaining the materialized view, adding CPU, logging, locking and storage work.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.