Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Applies to:
SQL Server 2025 (17.x)
Azure SQL Database
SQL database in Microsoft Fabric
Optional parameter plan optimization (OPPO) improves query plan quality for queries that include optional parameters. In these queries, the optimal execution plan depends on whether a parameter value is NULL at execution time. The term optional parameters refers to a specific variation of the parameter-sensitive plan (PSP) problem, in which the parameter value at execution time determines whether the query requires a seek or a scan.
Overview
Queries that use optional parameters often include predicates that conditionally apply filters based on whether a parameter value is provided. A common pattern is as follows:
SELECT column1,
column2
FROM Table1
WHERE (column1 = @p
OR @p IS NULL);
When @p IS NOT NULL, an index seek on col1 is often the most efficient execution plan. When @p IS NULL, the predicate evaluates to TRUE, and a scan might be more appropriate. Without OPPO, the SQL Server Database Engine must compile and cache a single execution plan that's valid for both cases. Because a seek-based plan isn't valid when @p IS NULL, the optimizer often chooses a conservative scan-based plan for all executions. This choice can result in inefficient plan choices and excessive resource usage for selective executions.
Traditional hinting techniques such as OPTIMIZE FOR aren't effective in this scenario, because the plan must remain correct for both parameter states.
OPPO uses the adaptive plan optimization (Multiplan) infrastructure introduced with Parameter Sensitive Plan (PSP) optimization. This infrastructure generates and caches multiple execution plans for a single statement, which allows OPPO to make different assumptions based on the parameter values used in the query.
Terminology and how it works
OPPO builds on the adaptive plan optimization (Multiplan) framework, which is also used by Parameter Sensitive Plan optimization. By using Multiplan, the Database Engine can generate and cache multiple execution plans for a single query.
When the Database Engine detects an eligible optional parameter pattern, it creates:
- A dispatcher plan
- One or more query variants, each optimized for a specific parameter value state
At execution time:
- The Database Engine evaluates the parameter value.
- The Multiplan dispatcher selects the appropriate query variant.
- The selected query variant executes.
After the Database Engine selects a query variant, it simplifies predicates based on the actual parameter value. Consider the following expression:
@p1 IS NULL
In this example, the expression is simplified to a constant result for the selected variant. This constant result folding allows the optimizer to generate execution plans that aren't valid in a single reusable plan.
By selecting plans in this way, OPPO enables efficient execution for different parameter states without requiring query rewrites or manual query hints.
OPPO and PSP optimization address different variations of parameter-related plan issues:
PSP optimization selects plans based on estimated cardinality differences for equality or range predicates.
OPPO selects plans based on whether a parameter value is
NULL.
A single query might benefit from both or either feature depending on the predicates involved.
Supported query patterns
Optional parameter plan optimization applies to queries where NULL checks on parameters affect execution plan validity. As an example, consider an application web form for a realty company that allows for optional filtering on the number of bedrooms for a particular listing. OPPO applies to disjunctive optional parameter predicates such as:
SELECT *
FROM Properties
WHERE bedrooms = @bedrooms
OR @bedrooms IS NULL;
Even if parameter markers can sniff the @bedrooms = 10 parameter, and you know that the cardinality for the number of bedrooms is likely to be very low, the optimizer doesn't produce a plan that seeks on an index that exists on the bedroom column, because that isn't a valid plan for the case where @bedrooms is NULL. The generated plan doesn't include a scan of the index.
Imagine if you could rewrite this query as two separate statements. Depending on the runtime value of the parameter, you could evaluate the following example:
IF @bedrooms IS NULL
SELECT *
FROM Properties;
ELSE
SELECT *
FROM Properties
WHERE bedrooms = @bedrooms;
The feature can achieve this by using the Multiplan infrastructure, which allows a creation of a dispatcher plan that dispatches a query variant.
OPPO embeds a system-generated PLAN PER VALUE query hint (optional_predicate) in the plan metadata to associate each query variant with its parameter state. This hint is system generated and embedded within the query text of the plan. This hint isn't valid for use by an application or to be applied manually.
Continuing with the previous example,
SELECT *
FROM Properties
WHERE bedrooms = @bedrooms
OR @bedrooms IS NULL;
OPPO can generate two query variants that might have the following attributes added to them within the Showplan XML:
@bedroomsisNULL. The query variant folds predicates based on the parameter value, allowing a scan-based plan to be generated.SELECT * FROM Properties PLAN PER VALUE(ObjectID = 1234, QueryVariantID = 1, optional_predicate(@bedrooms is NULL))
@bedrooms IS NOT NULLSELECT * FROM Properties WHERE bedrooms = @bedrooms PLAN PER VALUE(ObjectID = 1234, QueryVariantID = 2, optional_predicate(@bedrooms is NULL))
Use optional parameter plan optimization
To enable OPPO for a database, the following prerequisites are required:
- The database must use compatibility level 170.
- The
OPTIONAL_PARAMETER_OPTIMIZATIONdatabase-scoped configuration must be enabled.
The OPTIONAL_PARAMETER_OPTIMIZATION database-scoped configuration is enabled by default, so a database using compatibility level 170 (the default in SQL Server 2025 (17.x)) uses OPPO by default.
You can ensure that a database uses OPPO in SQL Server 2025 (17.x) by executing the following statements:
ALTER DATABASE [<database-name-placeholder>]
SET COMPATIBILITY_LEVEL = 170;
ALTER DATABASE SCOPED CONFIGURATION
SET OPTIONAL_PARAMETER_OPTIMIZATION = ON;
To disable OPPO for a database, disable the OPTIONAL_PARAMETER_OPTIMIZATION database-scoped configuration:
ALTER DATABASE SCOPED CONFIGURATION
SET OPTIONAL_PARAMETER_OPTIMIZATION = OFF;
Use optional parameter plan optimization via query hinting
Use the DISABLE_OPTIONAL_PARAMETER_OPTIMIZATION query hint to disable OPPO for a given query. Specify the hint via the USE HINT clause. For more information, see Query hints.
This hint works under any compatibility level, and overrides the OPTIONAL_PARAMETER_OPTIMIZATION database-scoped configuration.
Specify the DISABLE_OPTIONAL_PARAMETER_OPTIMIZATION query hint directly in the query or via Query Store hints.
Extended Events
Use the following extended events for troubleshooting and diagnostics. These events are not required to use the feature.
optional_parameter_optimization_skipped_reason: Occurs when OPPO decides that a query isn't eligible for optimization. This extended event follows the same pattern as theparameter_sensitive_plan_optimization_skipped_reasonevent that PSP optimization uses. Since a query can generate both PSP optimization and OPPO query variants, check both events to understand why one or neither feature engaged.The following query shows all of the possible reasons why PSP was skipped:
SELECT map_value FROM sys.dm_xe_map_values WHERE [name] = 'opo_skipped_reason_enum' ORDER BY map_key;query_with_optional_parameter_predicate: This extended event follows the same pattern as thequery_with_parameter_sensitivityevent that PSP optimization uses. It includes the additional fields that are available in the improvements for PSP optimization.These fields display:
- the number of predicates that the feature found interesting,
- more details in JSON format regarding the interesting predicates, and
- whether OPPO is supported for the predicate or predicates.
Remarks
- The ShowPlan XML for a query variant looks similar to the following example. The predicates that the feature selects have their respective information added to the
PLAN PER VALUE(optional_predicate) hint.
<Batch>
<Statements>
<StmtSimple StatementCompId="4" StatementEstRows="1989" StatementId="1" StatementOptmLevel="FULL" StatementOptmEarlyAbortReason="GoodEnoughPlanFound" CardinalityEstimationModelVersion="170" StatementSubTreeCost="0.0563916" StatementText="SELECT PropertyId, AgentId, ListingPrice, ZipCode, SquareFootage, 
 Bedrooms, Bathrooms, ListingDescription
 FROM dbo.Property 
 WHERE (@AgentId IS NULL OR AgentId = @AgentId)
 AND (@ZipCode IS NULL OR ZipCode = @ZipCode)
 AND (@MinPrice IS NULL OR ListingPrice >= @MinPrice)
 AND (@HasDescription IS NULL OR 
 (@HasDescription = 1 AND ListingDescription IS NOT NULL) OR
 (@HasDescription = 0 AND ListingDescription IS NULL)) option (PLAN PER VALUE(ObjectID = 1269579561, QueryVariantID = 7, optional_predicate(@MinPrice IS NULL),optional_predicate(@ZipCode IS NULL),optional_predicate(@AgentId IS NULL)))" StatementType="SELECT" QueryHash="0x2F701925D1202A9F" QueryPlanHash="0xBA0B2B1A18AF1033" RetrievedFromCache="true" StatementSqlHandle="0x09000033F4BE101B2EE46B1615A038D422710000000000000000000000000000000000000000000000000000" DatabaseContextSettingsId="1" ParentObjectId="1269579561" StatementParameterizationType="1" SecurityPolicyApplied="false">
<StatementSetOptions ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="true" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="true" />
<Dispatcher>
<OptionalParameterPredicate>
<Predicate>
<ScalarOperator ScalarString="[@MinPrice] IS NULL">
<Compare CompareOp="IS">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@MinPrice" />
</Identifier>
</ScalarOperator>
<ScalarOperator>
<Const ConstValue="NULL" />
</ScalarOperator>
</Compare>
</ScalarOperator>
</Predicate>
</OptionalParameterPredicate>
<OptionalParameterPredicate>
<Predicate>
<ScalarOperator ScalarString="[@ZipCode] IS NULL">
<Compare CompareOp="IS">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@ZipCode" />
</Identifier>
</ScalarOperator>
<ScalarOperator>
<Const ConstValue="NULL" />
</ScalarOperator>
</Compare>
</ScalarOperator>
</Predicate>
</OptionalParameterPredicate>
<OptionalParameterPredicate>
<Predicate>
<ScalarOperator ScalarString="[@AgentId] IS NULL">
<Compare CompareOp="IS">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@AgentId" />
</Identifier>
</ScalarOperator>
<ScalarOperator>
<Const ConstValue="NULL" />
</ScalarOperator>
</Compare>
</ScalarOperator>
</Predicate>
</OptionalParameterPredicate>
</Dispatcher>
<QueryPlan DegreeOfParallelism="1" CachedPlanSize="40" CompileTime="1" CompileCPU="1" CompileMemory="376" QueryVariantID="7">
Example output from the
query_with_optional_parameter_predicateextended event:Field Value optional_parameter_optimization_supportedTrue optional_parameter_predicate_count3 predicate_details{"Predicates":[{"Skewness":1005.53},{"Skewness":1989.00},{"Skewness":1989.00}]}query_type193
Query eligibility and limitations
OPPO applies only to queries that are eligible for Multiplan optimization. The feature isn't applied in scenarios that include:
- Queries that use local variables instead of parameters
- Queries compiled with
OPTION (RECOMPILE) - Queries executed with
SET ANSI_NULLS OFF - Auto-parameterized statements