How was a plan forced?

If you use Automatic Plan Correction, and thus also Query Store, you may wonder how was a plan forced: manually or automatically with APC?  The type of forced plan is tracked in sys.query_store_plan, and you can use this simple query to determine how a plan was forced:

SELECT
   p.is_forced_plan,
   p.plan_forcing_type,
   p.plan_forcing_type_desc
FROM sys.query_store_plan p
WHERE p.is_forced_plan = 1;
GO
Manual vs. Automatically Forced
Manual vs. Automatically Forced

Now, this information is a good start, but we can do better if we join to sys.dm_db_tuning_recommendations and the other Query Store view.  The query below is not pretty I know 🙂 but it does the job:

;WITH ForcedPlanInfo
AS
   (SELECT
      [FP].[QueryId] [QueryID],
      [Reason],
      JSON_VALUE(state, '$.currentValue') [CurrentValue],
      [FP].[RegressedPlanId] [RegressedPlanID],
      [FP].[ForcedPlanID] [ForcedPlanID],
      [execute_action_start_time],
      JSON_VALUE(details, '$.implementationDetails.script') [Script]
   FROM [sys].[dm_db_tuning_recommendations]
   CROSS APPLY OPENJSON (Details, '$.planForceDetails')
   WITH (
      [QueryId] INT '$.queryId',
      [RegressedPlanId] INT '$.regressedPlanId',
      [ForcedPlanID] INT '$.recommendedPlanId'
   ) AS FP
)
SELECT
   qt.query_text_id,
   q.query_id,
   p.plan_id,
   p.is_forced_plan,
   p.plan_forcing_type,
   p.plan_forcing_type_desc,
   qt.query_sql_text,
   TRY_CAST(p.query_plan AS XML),
   tr.QueryID,
   tr.Reason,
   tr.CurrentValue,
   tr.RegressedPlanID,
   tr.ForcedPlanID,
   tr.execute_action_start_time,
   tr.Script
FROM sys.query_store_plan p
LEFT OUTER JOIN ForcedPlanInfo tr
   ON p.plan_id = tr.ForcedPlanID
JOIN sys.query_store_query q
   ON p.query_id = q.query_id
JOIN sys.query_store_query_text qt
   ON q.query_text_id = qt.query_text_id
WHERE p.is_forced_plan = 1;
GO
Plan Forcing Output Part 1
Plan Forcing Output Part 1
Plan Forcing Output Part 2
Plan Forcing Output Part 2

As you can see, I broke the query output into two parts because it’s fairly wide, and for the plan that was forced manually, there are columns that are not relevant.

The only additional item I would want to display in this output is the user that forced the plan manually.  Unfortunately, this information is not currently captured by SQL Server.  You will not find it in an Extended Event or in the SQL Server ERRORLOG; hopefully that will be addressed soon.

Hope this helps those of you that are using Query Store and Automatic Plan Correction!

3 thoughts on “How was a plan forced?

  1. All of your single quotes when you copy and paste into SSMS are the incorrect ones so you have to replace them or it will error when executed.

Leave a Reply

Your email address will not be published. Required fields are marked *

Other articles

A Fond Farewell

If you haven’t guessed from the title, I’m writing this post because I am leaving SQLskills. This Friday, January 14th, is my last day, and

Explore

Imagine feeling confident enough to handle whatever your database throws at you.

With training and consulting from SQLskills, you’ll be able to solve big problems, elevate your team’s capacity, and take control of your data career.