Override the Query Optimizer for your T-SQL Joins with FORCEPLAN

The SET FORCEPLAN statement overrides the logic used by the SQL Server query optimizer to process a T-SQL SELECT statement.

More specifically, when FORCEPLAN is set to ON, the query optimizer processes a join in the same order as the tables appear in the FROM clause of a query.

This also forces the use of a nested loop join unless other types of joins are required to construct a plan for the query, or they are requested with join hints or query hints.

Example

To demonstrate how FORCEPLAN works, I’m going to run two SELECT queries, first with FORCEPLAN set to ON, then with FORCEPLAN set to OFF.

Both queries are identical, with the exception that the join tables are in listed a different order.

In this example I use SHOWPLAN_XML to show the estimated query plan, but you could just as easily use another method (such as the Explain button in Azure Data Studio, or the Include Actual Execution Plan icon in SSMS to display the actual query plan).

SET FORCEPLAN ON

SET FORCEPLAN ON;
GO

SET SHOWPLAN_XML ON;
GO

SELECT
    ar.ArtistName,
    al.AlbumName,
    g.Genre
FROM 
    Artists ar 
    INNER JOIN Albums al 
    ON ar.ArtistId = al.ArtistId 
    INNER JOIN Genres g 
    ON al.GenreId = g.GenreId;

SELECT
    ar.ArtistName,
    al.AlbumName,
    g.Genre
FROM 
    Albums al
    INNER JOIN Genres g 
    ON al.GenreId = g.GenreId
    INNER JOIN Artists ar 
    ON ar.ArtistId = al.ArtistId;

Result:

Screenshot of query plan with FORCEPLAN set to ON

We can see that the query plan for each query reflects the order in which I included the table names in the FROM clause.

SET FORCEPLAN OFF

SET SHOWPLAN_XML OFF;
GO

SET FORCEPLAN OFF;
GO

SET SHOWPLAN_XML ON;
GO

SELECT
    ar.ArtistName,
    al.AlbumName,
    g.Genre
FROM 
    Artists ar 
    INNER JOIN Albums al 
    ON ar.ArtistId = al.ArtistId 
    INNER JOIN Genres g 
    ON al.GenreId = g.GenreId;

SELECT
    ar.ArtistName,
    al.AlbumName,
    g.Genre
FROM 
    Albums al
    INNER JOIN Genres g 
    ON al.GenreId = g.GenreId
    INNER JOIN Artists ar 
    ON ar.ArtistId = al.ArtistId;

Result:

Screenshot of query plan with FORCEPLAN set to OFF

This time, both queries result in an identical query plan. The query optimizer ignored the order in which I listed them in the FROM clause and determined its own order.

Note that the FORCEPLAN setting does not change the data returned by the SELECT statement. The actual results are the same regardless of whether FORCEPLAN is set to ON or OFF. The only difference is the way in which tables are processed (which could impact on performance).

You can use SET FORCEPLAN in conjunction with query optimizer hints to further affect how the query is processed.