Introduction
Have you ever worked with a SQL Server table that contains millions of records, but your application regularly queries only a small portion of that data?
For example, an order management system may store every order created over several years. However, the application's dashboard may display only orders that are still pending.
Creating an index on the entire table can help, but it may also increase storage requirements and the cost of maintaining the index when records are inserted or updated.
SQL Server filtered indexes offer another option. They allow you to create an index that contains only the rows matching a specific filter condition.
Instead of indexing every order, you could create an index that contains only pending orders. This can reduce the size of the index and help SQL Server find the relevant records more efficiently.
However, filtered indexes are not suitable for every query. Their effectiveness depends on the filter condition, query structure, data distribution, and execution plan.
In this article, we will learn how filtered indexes work, how to create them, when they improve performance, and what to check when SQL Server does not use them.
What Is a Filtered Index in SQL Server?
A filtered index is a nonclustered index that contains only the rows satisfying a specified filter condition.
Consider an Orders table with the following columns:
OrderIdCustomerIdOrderDateStatusTotalAmount
Suppose the table contains several million orders, but the application frequently retrieves orders with a status of Pending.
A regular index may contain entries for orders with every status. A filtered index can contain entries only for pending orders.
Here is an example:
CREATE NONCLUSTERED INDEX IX_Orders_Pending
ON dbo.Orders (OrderDate)
INCLUDE (CustomerId, TotalAmount)
WHERE Status = 'Pending';This index contains entries only for rows where Status is Pending.
The index key is OrderDate, while CustomerId and TotalAmount are included as non-key columns.
This design can help queries that filter by pending status and need the indexed columns. SQL Server may be able to retrieve the required information directly from the index without reading the underlying table or clustered index for every matching row.
The actual benefit depends on the query and the execution plan.
How Does a Filtered Index Work?
Imagine an order table containing one million records:
Order status | Number of orders |
|---|---|
Pending | 20,000 |
Processing | 80,000 |
Completed | 800,000 |
Cancelled | 100,000 |
Total | 1,000,000 |
These numbers are illustrative.
If the application regularly queries pending orders, an index containing only those 20,000 records may be considerably smaller than an index covering all one million orders.
When SQL Server can use the filtered index, it has fewer index entries to search and maintain than it would with an equivalent full-table index.
However, a filtered index does not automatically make every query faster. SQL Server must determine that the index can satisfy the query and that using it is a good choice based on the estimated cost.
Filtered Index vs. Regular Index
Both regular and filtered indexes can improve query performance, but they serve different needs.
Feature | Regular Nonclustered Index | Filtered Index |
|---|---|---|
Rows indexed | Generally all eligible rows | Only rows matching the filter |
Index size | Can be larger | Often smaller when the filter selects a small subset |
Storage requirements | Depend on indexed columns and row count | Depend on matching rows and indexed columns |
Maintenance | Updated when relevant indexed data changes | Updated when changes affect rows included in the filter |
Best use | Queries that access a broad range of records | Queries that repeatedly access a specific subset |
Filter condition | No row filter | Requires a supported filter predicate |
A filtered index is not always better than a regular index. If most rows match the filter, the size advantage may be small.
When Should You Use a Filtered Index?
Filtered indexes are particularly useful when your application repeatedly queries a well-defined subset of a table.
1. Index pending orders
Suppose your application needs to display pending orders, sorted by creation date.
CREATE NONCLUSTERED INDEX IX_Orders_Pending
ON dbo.Orders (OrderDate)
INCLUDE (CustomerId, TotalAmount)
WHERE Status = 'Pending';The query might look like this:
SELECT
OrderDate,
CustomerId,
TotalAmount
FROM dbo.Orders
WHERE Status = 'Pending'
ORDER BY OrderDate;The query predicate matches the filtered index condition. SQL Server can consider the index for filtering and ordering, and the included columns may allow it to return the requested data without additional lookups.
This is a good candidate for a filtered index if pending orders represent a relatively small portion of the table and the query runs frequently.
2. Index records that have not been processed
Consider a background processing system that regularly checks for records that have not yet been processed.
CREATE NONCLUSTERED INDEX IX_Jobs_NotProcessed
ON dbo.Jobs (CreatedAt)
INCLUDE (JobType)
WHERE IsProcessed = 0;The application can use a query such as:
SELECT
CreatedAt,
JobType
FROM dbo.Jobs
WHERE IsProcessed = 0
ORDER BY CreatedAt;If only a small percentage of jobs remain unprocessed, the filtered index may reduce the amount of index data SQL Server needs to examine.
As jobs are processed and IsProcessed changes to 1, their entries no longer belong in the filtered index.
This makes filtered indexes useful for certain queue-like tables, although the overall design must still account for concurrent workers and how jobs are claimed.
3. Index active records
Some applications keep inactive records for auditing but frequently retrieve only active ones.
CREATE NONCLUSTERED INDEX IX_Customers_Active
ON dbo.Customers (LastName, FirstName)
INCLUDE (Email)
WHERE IsActive = 1;This index may help when the application searches or sorts active customers by name.
Before creating it, check whether the application also performs queries that need inactive customers. Those queries may require a different index or access path.
How to Check Whether SQL Server Uses the Filtered Index
Creating an index does not guarantee that SQL Server will use it.
First, enable the actual execution plan in SQL Server Management Studio. Then run the query you want to optimize.
SELECT
OrderDate,
CustomerId,
TotalAmount
FROM dbo.Orders
WHERE Status = 'Pending'
ORDER BY OrderDate;Review the execution plan for an index seek or scan using IX_Orders_Pending.
An index seek can be efficient when SQL Server needs only a small range of entries. An index scan can also be appropriate if many entries need to be read.
Do not judge performance by the operator name alone. Compare the number of rows read, logical reads, execution time, and any additional lookups.
You can also collect basic I/O and timing information:
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT
OrderDate,
CustomerId,
TotalAmount
FROM dbo.Orders
WHERE Status = 'Pending'
ORDER BY OrderDate;
SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;Run the query with representative parameters and data. Compare the results before and after creating the index, and account for caching and workload variation.
When Filtered Indexes Do Not Help
Filtered indexes are useful, but there are situations where they provide little benefit or are not eligible for a particular query.
The filter matches most of the table
If 95 percent of the records satisfy the filter, the index may still contain most of the table's rows.
A regular index may be more appropriate, depending on the query and the columns involved.
The query does not imply the filter condition
Suppose you create this index:
CREATE NONCLUSTERED INDEX IX_Orders_Pending
ON dbo.Orders (OrderDate)
WHERE Status = 'Pending';A query that retrieves all orders cannot use this index alone to return the complete result because the index excludes orders with other statuses.
SQL Server must be able to prove that the filtered index contains all rows required by the query.
The query uses a parameterized predicate
Parameterized queries are generally good for security and plan reuse, but they can complicate filtered-index selection.
For example:
SELECT
OrderDate,
CustomerId,
TotalAmount
FROM dbo.Orders
WHERE Status = @Status;If @Status can contain different values, SQL Server may need a plan that works for values other than Pending. Depending on parameterization and plan compilation, the filtered index may not be eligible or selected.
Do not solve this by automatically concatenating user input into dynamic SQL. First inspect the execution plan and determine whether the query can safely be structured to make the filter condition clear.
Best Practices for Filtered Indexes
1. Choose a filter based on real query patterns
Use query logs, Query Store, or workload analysis to identify frequently executed queries that access a specific subset of records.
Avoid creating filtered indexes simply because a column contains values such as Active, Pending, or IsDeleted.
2. Keep the index as narrow as possible
Choose key columns that support filtering, sorting, or joins. Use included columns for additional values needed by the query.
Adding unnecessary columns increases storage and maintenance costs.
3. Check the execution plan before and after
Verify that SQL Server uses the index appropriately and that the change improves the workload. Compare logical reads and execution time rather than assuming a smaller index is always faster.
4. Consider data changes
Filtered indexes are maintained when changes cause rows to enter or leave the filtered set.
For example, changing an order from Pending to Completed removes its entry from the pending index. If your application changes status frequently, include that maintenance cost in your evaluation.
5. Maintain appropriate statistics
SQL Server maintains statistics for indexes, including filtered indexes. Good statistics help the optimizer estimate the number of matching rows.
Investigate stale or inaccurate estimates when the execution plan does not match the actual workload.
Common Mistakes and Troubleshooting
Problem | Possible reason | Recommended action |
|---|---|---|
SQL Server ignores the filtered index | Query predicate does not imply the filter | Check whether the query returns only rows covered by the index |
Query performance does not improve | The query reads most of the matching rows or another operation dominates | Review logical reads and the actual execution plan |
Index is larger than expected | The filter matches many rows or included columns are too wide | Review the filter selectivity and index design |
Parameterized query does not use the index | The optimizer cannot safely rely on the parameter value | Inspect compilation and plan selection |
Writes become slower | The index adds maintenance work | Measure write overhead and remove unnecessary indexes |
Index usage changes over time | Data distribution or query patterns have changed | Reassess the index using current workload information |
One common mistake is creating several filtered indexes for every possible status. Each additional index consumes storage and adds maintenance work when relevant records change.
Start with the indexes that support important queries, and remove those that do not provide enough benefit.
Advantages and Disadvantages of Filtered Indexes
Advantages
- Can reduce index size when only a small subset of rows is relevant.
- May improve queries that repeatedly access a specific subset of data.
- Can reduce storage and maintenance compared with an equivalent full-table index.
- Can provide useful filtered statistics for the optimizer.
- May avoid extra lookups when the index covers the query.
Disadvantages
- Only applies to queries that can use the filtered subset.
- Offers limited size benefits when most rows match the filter.
- Adds maintenance overhead when rows enter or leave the filtered set.
- May not be selected for some parameterized queries.
- Requires careful testing as data distribution and workload change.
Summary
SQL Server filtered indexes are useful when your application regularly queries a small, well-defined portion of a large table.
They can help with scenarios such as pending orders, unprocessed jobs, and active customer records. By indexing only the relevant rows, you may reduce index size and improve query performance.
However, a filtered index is not a universal replacement for a regular index. Its value depends on the filter condition, the query structure, data distribution, and the work SQL Server must perform.
Before adding one, identify a real performance problem, inspect the execution plan, and compare logical reads and execution time. Then monitor the index as your data and workload evolve.
The best filtered index is one that supports a real query pattern and provides measurable benefits without adding unnecessary maintenance cost.
Join the conversation! Your thoughts help the community grow.