diff --git a/samples/databases/README.md b/samples/databases/README.md
index 0f959213..016a175f 100644
--- a/samples/databases/README.md
+++ b/samples/databases/README.md
@@ -9,3 +9,7 @@ The new sample database for SQL Server 2016 and Azure SQL Database. It illustrat
__[contoso-data-warehouse](contoso-data-warehouse/)__
Sample data warehouse that illustrates loading data into Azure SQL Data Warehouse.
+
+__[AdventureWorks2014](https://github.com/Microsoft/sql-server-samples/releases/tag/adventureworks2014)__
+
+Sample databases and Analysis Services models for use with SQL Server 2014 and later.
\ No newline at end of file
diff --git a/samples/features/automatic-tuning/force-last-good-plan/force-last-good-plan.csproj b/samples/features/automatic-tuning/force-last-good-plan/force-last-good-plan.csproj
new file mode 100644
index 00000000..809a941a
--- /dev/null
+++ b/samples/features/automatic-tuning/force-last-good-plan/force-last-good-plan.csproj
@@ -0,0 +1,36 @@
+
+
+
+ netcoreapp1.0
+ true
+ force-last-good-plan
+ Exe
+ force-last-good-plan
+ 1.0.4
+ $(PackageTargetFallback);dotnet5.6;portable-net45+win8
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/demo-full.sql b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/demo-full.sql
index 0fed3f0b..1dd38d35 100644
--- a/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/demo-full.sql
+++ b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/demo-full.sql
@@ -1,60 +1,74 @@
/********************************************************
* SETUP - clear everything
********************************************************/
-EXEC [dbo].[initialize]
-
+ALTER DATABASE current SET AUTOMATIC_TUNING (FORCE_LAST_GOOD_PLAN = OFF);
+EXEC dbo.initialize;
/********************************************************
* PART I
-* Plan regression identification.
+* Plan regression identification & manual tuning
********************************************************/
--- 1. Start workload - execute procedure 30-300 times:
-begin
-declare @packagetypeid int = 7;
-exec dbo.report @packagetypeid
-end
-go 300
--- Queries should be fast
--- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Hash Aggregate)
+-- Execute the query and include "Actual execution plan" in SSMS and show the plan - it should have Hash Match (Aggregate) operator with Columnstore Index Scan
+EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
+ from Sales.OrderLines
+ where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
+GO 60
+-- 1. Execute this query 45-300 times to setup the baseline.
+-- If you have QUERY_STORE CAPTURE_POLICY=AUTO increase number in GO to at least 60
--- 2. Execute procedure that causes plan regression
--- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Stream Aggregate)
-exec dbo.regression
+-- 2. Execute the procedure that causes plan regression
+-- Optionally, include "Actual execution plan" in SSMS and show the plan - it should have Stream Aggregate, Index Seek & Nested Loops
+EXEC dbo.regression;
--- 3. Start workload again - verify that is slower.
-begin
-declare @packagetypeid int = 7;
-exec dbo.report @packagetypeid
-end
+-- 3. Start the workload again - verify that is slower.
+EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
+ from Sales.OrderLines
+ where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
go 20
--- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Stream Aggregate)
+-- Optionally, include "Actual execution plan" in SSMS and show the plan - it should have Stream Aggregate with Non-clustered index seek.
--- 4. Find recommendation recommended by database:
-SELECT planForceDetails.query_id, reason, score,
- JSON_VALUE(details, '$.implementationDetails.script') [correction script],
- planForceDetails.[new plan_id], planForceDetails.[recommended plan_id]
-FROM sys.dm_db_tuning_recommendations
- CROSS APPLY OPENJSON (Details, '$.planForceDetails')
- WITH ( [query_id] int '$.queryId',
- [new plan_id] int '$.regressedPlanId',
- [recommended plan_id] int '$.recommendedPlanId'
- ) as planForceDetails;
+-- 4. Find a recommendation that can fix this issue:
+SELECT reason, score,
+ script = JSON_VALUE(details, '$.implementationDetails.script')
+ FROM sys.dm_db_tuning_recommendations;
+-- 4.1. Optionally get more detailed information about the regression and recommendation.
+SELECT reason, score,
+ script = JSON_VALUE(details, '$.implementationDetails.script'),
+ planForceDetails.[query_id],
+ planForceDetails.[new plan_id],
+ planForceDetails.[recommended plan_id],
+ estimated_gain = (regressedPlanExecutionCount+recommendedPlanExecutionCount)*(regressedPlanCpuTimeAverage-recommendedPlanCpuTimeAverage)/1000000,
+ error_prone = IIF(regressedPlanErrorCount>recommendedPlanErrorCount, 'YES','NO')
+ FROM sys.dm_db_tuning_recommendations
+ CROSS APPLY OPENJSON (Details, '$.planForceDetails')
+ WITH ( [query_id] int '$.queryId',
+ [new plan_id] int '$.regressedPlanId',
+ [recommended plan_id] int '$.recommendedPlanId',
+ regressedPlanErrorCount int,
+ recommendedPlanErrorCount int,
+ regressedPlanExecutionCount int,
+ regressedPlanCpuTimeAverage float,
+ recommendedPlanExecutionCount int,
+ recommendedPlanCpuTimeAverage float ) as planForceDetails;
+-- IMPORTANT NOTE: check is estimated_gain > 10.
+-- If estimated_gain < 10 THEN FLGP=ON will not automatically force the plan!!!
+-- In that case increase the number of executions in initial workload.
+-- Make sure that SQL Engine uses columnstore in original plan and nonclustered index in regressed plan.
-- Note: User can apply script and force the recommended plan to correct the error.
<>
-- e.g.: exec sp_query_store_force_plan @query_id = 3, @plan_id = 1
--- 5. Start workload again - verify that is faster.
-begin
-declare @packagetypeid int = 7;
-exec dbo.report @packagetypeid
-end
-go 20
--- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Hash Aggregate again)
+-- 5. Execute the query again - verify that it is faster.
+EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
+ from Sales.OrderLines
+ where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
+GO 20
+-- Optionally, include "Actual execution plan" in SSMS and show the plan - it should have Hash Aggregate & Columnstore again
-- In part II will be shown better approach - automatic tuning.
@@ -74,29 +88,26 @@ ALTER DATABASE current
SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON);
-- Verify that actual state on FLGP is ON:
-SELECT name, desired_state_desc, actual_state_desc, reason_desc
-FROM sys.database_automatic_tuning_options;
-
+SELECT name, actual_state_desc, status = IIF(desired_state_desc <> actual_state_desc, reason_desc, 'Status:OK')
+FROM sys.database_automatic_tuning_options
+WHERE name = 'FORCE_LAST_GOOD_PLAN';
-- 1. Start workload - execute procedure 30-300 times like in the phase I
-begin
-declare @packagetypeid int = 7;
-exec dbo.report @packagetypeid
-end
-go 300
+EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
+ from Sales.OrderLines
+ where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
+GO 60
--- 2. Execute the procedure that causes plan regression
+-- 2. Execute the procedure that causes the plan regression
exec dbo.regression;
--- 3. Start workload again - verify that it is slower.
-begin
-declare @packagetypeid int = 7;
-exec dbo.report @packagetypeid;
-end
-go 20
+-- 3. Start the workload again - verify that it is slower.
+EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
+ from Sales.OrderLines
+ where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
+go 30
--- 4. Find recommendation that returns query perf regression
--- and check is it in Verifying state:
+-- 4. Find a recommendation and check is it in "Verifying" or "Success" state:
SELECT reason, score,
JSON_VALUE(state, '$.currentValue') state,
JSON_VALUE(state, '$.reason') state_transition_reason,
@@ -110,11 +121,11 @@ FROM sys.dm_db_tuning_recommendations
) as planForceDetails;
--- 5. Wait until recommendation is applied and start workload again - verify that it is faster.
-begin
-declare @packagetypeid int = 7;
-exec dbo.report @packagetypeid
-end
-go 30
+-- 5. Recommendation is in "Verifying" state, but the last good plan is forced, so the query will be faster:
+EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
+ from Sales.OrderLines
+ where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
+
+
+-- Open Query Store/"Top Resource Consuming Queries" dialog in SSMS and show that the better plan is forced.
--- Open Query Store/"Top Resource Consuming Queries" dialog in SSMS and show that better plan is forced.
\ No newline at end of file
diff --git a/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/init-blank-db.sql b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/init-blank-db.sql
new file mode 100644
index 00000000..95063b8d
--- /dev/null
+++ b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/init-blank-db.sql
@@ -0,0 +1,64 @@
+/***************************************************************************
+* Run this script on a empty database if you don't have WWI database and
+* you want to use new database instead of full WWI
+* If you are using SSMS, use Ctrl+Shift+M to populate parameters.
+***************************************************************************/
+ALTER DATABASE MODIFY (EDITION = 'Premium', SERVICE_OBJECTIVE = '');
+SELECT DATABASEPROPERTYEX('', 'ServiceObjective');
+-- Create minimal WWI schema required to run the sample:
+DROP TABLE IF EXISTS [Sales].[OrderLines];
+GO
+DROP SEQUENCE IF EXISTS [Sequences].[OrderLineID];
+GO
+DROP SCHEMA IF EXISTS [Sequences];
+GO
+DROP SCHEMA IF EXISTS [Sequences];
+GO
+
+CREATE SCHEMA [Sequences];
+GO
+CREATE SEQUENCE [Sequences].[OrderLineID]
+ AS [int]
+ START WITH 231413
+ INCREMENT BY 1
+ MINVALUE -2147483648
+ MAXVALUE 2147483647
+ CACHE
+GO
+
+CREATE TABLE [Sales].[OrderLines](
+ [OrderLineID] [int] PRIMARY KEY,
+ [OrderID] [int] NOT NULL,
+ [StockItemID] [int] NOT NULL,
+ [Description] [nvarchar](100) NOT NULL,
+ [PackageTypeID] [int] NOT NULL,
+ [Quantity] [int] NOT NULL,
+ [UnitPrice] [decimal](18, 2) NULL,
+ [TaxRate] [decimal](18, 3) NOT NULL,
+ [PickedQuantity] [int] NOT NULL,
+ [PickingCompletedWhen] [datetime2](7) NULL,
+ [LastEditedBy] [int] NOT NULL,
+ [LastEditedWhen] [datetime2](7) NOT NULL
+)
+GO
+ALTER TABLE [Sales].[OrderLines]
+ ADD CONSTRAINT [DF_Sales_OrderLines_OrderLineID]
+ DEFAULT (NEXT VALUE FOR [Sequences].[OrderLineID]) FOR [OrderLineID]
+GO
+ALTER TABLE [Sales].[OrderLines]
+ ADD CONSTRAINT [DF_Sales_OrderLines_LastEditedWhen]
+ DEFAULT (sysdatetime()) FOR [LastEditedWhen]
+GO
+DROP INDEX IF EXISTS [FK_Sales_OrderLines_PackageTypeID]
+ ON [Sales].[OrderLines]
+
+CREATE NONCLUSTERED INDEX [FK_Sales_OrderLines_PackageTypeID]
+ ON [Sales].[OrderLines]([PackageTypeID] ASC)
+GO
+
+
+-- Export Sales.OrderLines from WWI database using bcp:
+-- bcp WideWorldImporters.Sales.OrderLines out OrderLines.dat -T -c -U -P -S
+
+-- Import data in new database using bcp:
+-- bcp .Sales.OrderLines in OrderLines.dat -c -U -P -S
diff --git a/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/setup.sql b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/setup.sql
index b1dd01a1..382ef397 100644
--- a/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/setup.sql
+++ b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/setup.sql
@@ -1,6 +1,10 @@
-DROP INDEX IF EXISTS [NCCX_Sales_OrderLines] ON [Sales].[OrderLines]
+-- Insert one OrderLine that with PackageTypeID=(0) will cause regression
+INSERT INTO Sales.OrderLines(OrderId, StockItemID, Description, PAckageTypeID, quantity, unitprice, taxrate, PickedQuantity,LastEditedBy)
+SELECT TOP 1 OrderID, StockItemID, Description, PackageTypeID = 0, Quantity, UnitPrice, taxrate , PickedQuantity,LastEditedBy
+FROM Sales.OrderLines;
+
+DROP INDEX IF EXISTS [NCCX_Sales_OrderLines] ON [Sales].[OrderLines]
-/****** Object: Index [NCCX_Sales_OrderLines] Script Date: 4/20/2017 11:27:27 AM ******/
CREATE NONCLUSTERED COLUMNSTORE INDEX [NCCX_Sales_OrderLines] ON [Sales].[OrderLines]
(
[OrderID],
@@ -10,72 +14,74 @@ CREATE NONCLUSTERED COLUMNSTORE INDEX [NCCX_Sales_OrderLines] ON [Sales].[OrderL
[UnitPrice],
[PickedQuantity],
[PackageTypeID] -- adding package type id for demo purpose
-)WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0) ON [USERDATA]
+)WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0)
GO
CREATE OR ALTER PROCEDURE [dbo].[initialize]
-as begin
+AS BEGIN
-ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
-ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
-ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = OFF);
+ ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
+ ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
-end
+END
GO
CREATE OR ALTER PROCEDURE [dbo].[report] (@packagetypeid int)
-as begin
+AS BEGIN
-select avg([UnitPrice]*[Quantity])
-from Sales.OrderLines
-where PackageTypeID = @packagetypeid
+EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
+ from Sales.OrderLines
+ where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid;
-end
+END
GO
CREATE OR ALTER PROCEDURE [dbo].[regression]
-as begin
+AS BEGIN
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
-begin
- declare @packagetypeid int = 1;
+BEGIN
+ declare @packagetypeid int = 0;
exec report @packagetypeid;
-end
+END
-end
+END
GO
CREATE OR ALTER PROCEDURE [dbo].[auto_tuning_on]
-as begin
+AS BEGIN
-ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON);
-ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
-ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
+ ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON);
+ ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
+ ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
-end
+END
GO
CREATE OR ALTER PROCEDURE [dbo].[auto_tuning_off]
-as begin
+AS BEGIN
-ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = OFF);
-ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
-ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
+ ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = OFF);
+ ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
+ ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
-end
+END
GO
+/*
-CREATE EVENT SESSION [APC - plans that are not corrected] ON SERVER
-
+CREATE EVENT SESSION [APC - plans that are not corrected] ON DATABASE
ADD EVENT qds.automatic_tuning_plan_regression_detection_check_completed(
WHERE ((([is_regression_detected]=(1))
AND ([is_regression_corrected]=(0)))
AND ([option_id]=(1))))
-ADD TARGET package0.event_file(SET filename=N'plans_that_are_not_corrected')
+-- Use file target only on SQL Server 2017:
+-- ADD TARGET package0.event_file(SET filename=N'plans_that_are_not_corrected')
+ADD TARGET package0.ring_buffer (SET max_memory = 1000)
WITH (STARTUP_STATE=ON);
GO
-ALTER EVENT SESSION [APC - plans that are not corrected] ON SERVER STATE = start;
\ No newline at end of file
+ALTER EVENT SESSION [APC - plans that are not corrected] ON SERVER STATE = start;
+*/
\ No newline at end of file
diff --git a/samples/features/automatic-tuning/force-last-good-plan/wwwroot/index.html b/samples/features/automatic-tuning/force-last-good-plan/wwwroot/index.html
index fdfeafc5..10d8e3b9 100644
--- a/samples/features/automatic-tuning/force-last-good-plan/wwwroot/index.html
+++ b/samples/features/automatic-tuning/force-last-good-plan/wwwroot/index.html
@@ -37,27 +37,37 @@