Performance 02: Query Store regressions

Run it in the database: queries that got slower in the last 24 hours than in the 7 days before, usually because the plan changed. Needs Query Store turned on. Read-only, changes nothing. Tested on SQL Server 2017 and 2025.

-- 02_Query_Store_Regressions.sql - run IN the database: queries that got slower in the last 24 hours than in the 7 days before,
-- usually because the plan changed. Needs Query Store on (ALTER DATABASE ... SET QUERY_STORE = ON). Read-only.
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM sys.database_query_store_options WHERE actual_state_desc <> N'OFF')
BEGIN PRINT N'Query Store is off in ' + DB_NAME() + N'.'; RETURN; END;

WITH s AS (
    SELECT p.query_id, rs.plan_id,
           AVG(CASE WHEN i.start_time >= DATEADD(HOUR, -24, SYSUTCDATETIME()) THEN rs.avg_duration END) AS recent_us,
           AVG(CASE WHEN i.start_time <  DATEADD(HOUR, -24, SYSUTCDATETIME()) THEN rs.avg_duration END) AS before_us,
           SUM(rs.count_executions) AS executions
    FROM sys.query_store_runtime_stats AS rs
    JOIN sys.query_store_runtime_stats_interval AS i ON i.runtime_stats_interval_id = rs.runtime_stats_interval_id
    JOIN sys.query_store_plan AS p ON p.plan_id = rs.plan_id
    WHERE i.start_time >= DATEADD(DAY, -8, SYSUTCDATETIME())
    GROUP BY p.query_id, rs.plan_id)
SELECT TOP (25) s.query_id, s.plan_id, CONVERT(decimal(12,1), s.before_us / 1000) AS before_ms, CONVERT(decimal(12,1), s.recent_us / 1000) AS recent_ms,
       CONVERT(decimal(8,1), s.recent_us / NULLIF(s.before_us, 0)) AS times_slower, s.executions, qt.query_sql_text
FROM s JOIN sys.query_store_query AS q ON q.query_id = s.query_id
JOIN sys.query_store_query_text AS qt ON qt.query_text_id = q.query_text_id
WHERE s.recent_us > 2 * s.before_us AND s.recent_us > 100000
ORDER BY s.recent_us - s.before_us DESC;
-- To go back to the old plan: EXEC sp_query_store_force_plan @query_id = ..., @plan_id = ...;  (CAUTION: changes behaviour)