creating a plan guide - sql-server

On SQL Server, I am testing to create a Plan guide to force a query from Java to use a specific optimal execution-plan always. Some how the SQL query not using this Plan Guide
I followed, https://technet.microsoft.com/en-us/library/ms190772%28v=sql.105%29.aspx?f=255&MSPPError=-2147217396
SQL comes from Java,
declare #p1 int
set #p1=1986
exec sp_prepexec #p1 output,N'#P0 nvarchar(4000)',N'SELECT Top 1 Amount,col2 from dbo.table1
where ExId=#P0
order by id
',N'0a8e8e31-2a05-0000-8ece-0003fd69e692'
select #p1
Plan Guide I created,
DECLARE #xml_showplan nvarchar(max);
SET #xml_showplan = (select cast(query_plan as nvarchar(max)) from sys.dm_exec_cached_plans cp
cross apply sys.dm_exec_sql_text (cp.plan_handle) st
cross apply sys.dm_exec_query_plan (cp.plan_handle) qp
where st.text like '%Top 1 Amount%'
and objtype='Prepared')
--select (#xml_showplan)
EXEC sp_create_plan_guide
#name = N'ForceuserLoanAmountRequests',
#stmt = N'SELECT Top 1 Amount,col2 from dbo.table1 where ExId=#P0 order by id',
#type = N'SQL',
#module_or_batch = NULL,
#params = N'#P0 nvarchar(4000)',
#hints = #xml_showplan;
Appreciate if you can help me to get thru the plan guide used by SQL query from java.
Created a Plan Guide with the SQL collected from SQL Profiler,
EXEC sp_create_plan_guide
#name = N'ForceuserLoanAmountRequests',
#stmt = N'SELECT Top 1 Amount,col2 from table1
where ExId=#P0
order by ID
',
#type = N'SQL',
#module_or_batch = NULL,
#params = N'#P0 nvarchar(4000)',
#hints = #xml_showplan;
GO

Please read the article sp_create_plan_guide (Transact-SQL) pay attention to Remarks:
Plan Guide Matching Requirements
For plan guides that specify #type = 'SQL' or #type = 'TEMPLATE' to
successfully match a query, the values for batch_text and
#parameter_name data_type [,...n ] must be provided in exactly the
same format as their counterparts submitted by the application. This
means you must provide the batch text exactly as the SQL Server
compiler receives it. To capture the actual batch and parameter text,
you can use SQL Server Profiler. For more information, see Using SQL
Server Profiler to Create and Test Plan Guides.
This means that the statement should exactly match to what is written in your plan guide, but in your code plan guide is created for a statement that differs from those sending by java: it has only one row while java code has 4 rows with CR and additional spaces.
If you evaluate any hash from java statement and your statement provided in sp_create_plan_guide you'll see they are different.
Another thing I suspect that may be wrong is how you get a "good plan". Since you just assign #xml_showplan the result of a query that can produce many rows, I'm not sure you get the plan you want. How do you ensure that you capture the "good£ plan and not the same plan that java code produced?

Related

How to limit the rows on remote/linked server

I have a linked server that I have to fetch data from. I'm joining on a table that I expect very few rows from. The query is below, and seems to be returning all of the rows to the original server to do the sort there.
I'm looking for a way to tell the query to filter on the target machine, with a query hint or something else.
Query
INSERT INTO #DealerHierarchy(DealerId, Level)
SELECT cd.ParentId, cd.Level
FROM [dbo].[AssignedDealer] ad
JOIN [nlsdb].[nls].[dbo].[vw_parentDealers] cd ON cd.RootId = ad.DealerId
WHERE ad.UserId = #userId
AND ad.IsActive = 1
AND (#DealerId IS NULL OR ad.DealerId = #DealerId)
When I add the following line, it seems to change and only send back the needed rows
and cd.RootId = 72311
I have tried moving out the local query into a separate temp table, and then select from the view WHERE DealerId IN (select from temp table) but it still runs slowly. Adding the REMOTE hint in the JOIN also does nothing.
Query plan:
https://www.brentozar.com/pastetheplan/?id=r1iazaaFZ
Slow code executed on linked server
declare #p1 int
set #p1=7
exec sp_prepexec #p1 output,N'#P1 numeric(10)',N'SELECT "Tbl1007"."ParentId" "Col1010","Tbl1007"."Level" "Col1011" FROM "nls"."dbo"."vw_parentDealers" "Tbl1007" WHERE #P1="Tbl1007"."RootId"',72311
select #p1
Fast code executed on linked server
declare #p1 int
set #p1=10
exec sp_prepexec #p1 output,NULL,N'SELECT "Tbl1007"."ParentId" "Col1010","Tbl1007"."Level" "Col1011" FROM "nls"."dbo"."vw_parentDealers" "Tbl1007" WHERE "Tbl1007"."RootId"=(72311.)'
select #p1
You can force a specific query to be run on the remote database by using OPENQUERY. OPENQUERY doesn't accept a parameter, so you can make it dynamic by further wrapping it in EXEC.
Example
DECLARE #SearchString NVARCHAR = ...
DECLARE #OpenQueryString NVARCHAR = 'SELECT * FROM OPENQUERY(remotedb, ''' + #SearchString + ''')'
EXEC (#OpenQueryString)

SQL Server - Creating a Plan Guide without using an alias

I'm trying to create a plan guide in SQL Server 2012 SP3 Enterprise Edition for a specific query run by an application, which means I cannot alter the query in any way.
The query looks like this:
(#P1 nvarchar(5),#P2 bigint)
DELETE FROM INVENTSUMDELTA WHERE ((DATAAREAID=#P1) AND (TTSID=#P2))
To create the plan guide, I used the following query:
EXEC sp_create_plan_guide
#name = N'INVENTSUMDELTAINDEX',
#stmt = N'DELETE FROM INVENTSUMDELTA WHERE ((DATAAREAID=#P1) AND (TTSID=#P2))',
#type = N'SQL',
#module_or_batch = NULL,
#params = N'#P1 nvarchar(5),#P2 bigint',
#hints = N'OPTION (TABLE HINT ( INVENTSUMDELTA, INDEX( I_2397TTSDIMIDX )))';
However, I received an error:
Msg 8724, Level 16, State 1, Line 1
Cannot execute query. Table-valued or OPENROWSET function 'INVENTSUMDELTA' cannot be specified in the TABLE HINT clause.
I checked the documentation and found the following:
TABLE HINT (exposed_object_name [ , [ [, ]...n ] ] )
Applies the specified table hint to the table or view that corresponds to exposed_object_name. [...]
exposed_object_name can be one of the following references:
When an alias is used for the table or view in the FROM clause of the query, exposed_object_name is the alias.
When an alias is not used, exposed_object_name is the exact match of the table or view referenced in the FROM clause. For example, if the table or view is referenced using a two-part name, exposed_object_name is the same two-part name.
From this I gather that it should be possible to create a plan guide for a query that isn't using an alias. However, I cannot get it to work.
So my question is: how do I create a plan guide without using aliasing and without altering the original query?
The error message is misleading. It has nothing to do with the nature of the object (you will get the same error with a nonexistent object). The problem is that it will not work for DELETE statements -- any TABLE HINT referring to a table that is the target of a DELETE will produce this error. This is not restricted to plan guides either -- a plain DELETE with an option will fail as well:
DELETE FROM does_not_exist
OPTION (TABLE HINT (does_not_exist, INDEX (does_not_exist)))
Cannot execute query. Table-valued or OPENROWSET function 'does_not_exist' cannot be specified in the TABLE HINT clause.
This appears to be a bug, because if the statement is augmented with a WITH (ROWLOCK) hint at both the table and query level, the error disappears:
DELETE FROM does_not_exist WITH (ROWLOCK)
OPTION (TABLE HINT (does_not_exist, ROWLOCK, INDEX (does_not_exist)))
Invalid object name 'does_not_exist'.
The same issue is covered in this question, and the solution is to rewrite the query in a form that does allow applying the hint.
In this case, we can't rewrite the query directly, but we can still get the desired effect by using a fixed query plan guide:
-- Alternate query using hint.
DECLARE #sql NVARCHAR(MAX) = N'WITH T AS (
SELECT *
FROM INVENTSUMDELTA WITH (INDEX (I_2397TTSDIMIDX))
WHERE ((DATAAREAID=#P1) AND (TTSID=#P2))
)
DELETE T';
DECLARE #params NVARCHAR(MAX) = N'#P1 nvarchar(5),#P2 bigint'
-- Put the execution plan in the cache.
EXEC sp_executesql #sql, #params = #params, #P1=NULL, #P2=NULL;
-- Retrieve it.
DECLARE #query_plan NVARCHAR(MAX);
SELECT #query_plan = query_plan
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.[sql_handle]) AS st
CROSS APPLY sys.dm_exec_text_query_plan(qs.[plan_handle], DEFAULT, DEFAULT) AS qp
WHERE st.[text] LIKE '(' + #params + ')%' + #sql;
-- Create a plan guide associating the query with the new execution plan.
EXEC sp_create_plan_guide
#name = N'INVENTSUMDELTAINDEX',
#stmt = N'DELETE FROM INVENTSUMDELTA WHERE ((DATAAREAID=#P1) AND (TTSID=#P2))',
#type = N'SQL',
#module_or_batch = NULL,
#params = #params,
#hints = #query_plan;
As always, plan guides should be the last resort if nothing else helps (updating statistics, creating new indexes, dropping suboptimal indexes). This answer assumes you've reviewed all the other options and the plan guide is necessary.

How to insert into table the results of a dynamic query when the schema of the result is unknown a priori?

Observe the following simple SQL code:
CREATE TABLE #tmp (...) -- Here comes the schema
INSERT INTO #tmp
EXEC(#Sql) -- The #Sql is a dynamic query generating result with a known schema
All is good, because we know the schema of the result produced by #Sql.
But what if the schema is unknown? In this case I use Powershell to generate a Sql query like that:
SET #Sql = '
SELECT *
INTO ##MySpecialAndUniquelyNamedGlobalTempTable
FROM ($Query) x
'
EXEC(#Sql)
(I omit some details, but the "spirit" of the code is preserved)
And it works fine, except that there is a severe limitation to what $Query can be - it must be a single SELECT statement.
This is not very good for me, I would like to be able to run any Sql script like that. The problem, is that no longer can I concatenate it to FROM (, it must be executed by EXEC or sp_executesql. But then I have no idea how to collect the results into a table, because I have no idea of the schema of that table.
Is it possible in Sql Server 2012?
Motivation: We have many QA databases across different Sql servers and more often than not I find myself running queries on all of them in order to locate the database most likely to yield best results for my tests. Alas, I am only able to run single SELECT statements, which is inconvenient.
We use SP and OPENROWSET for this purpose.
At first create SP based on a query you need, than use OPENROWSET to get data into temp table:
USE Test
DECLARE #sql nvarchar(max),
#query nvarchar(max)
SET #sql = N'Some query'
IF OBJECT_ID(N'SomeSPname') IS NOT NULL DROP PROCEDURE SomeSPname
SET #query =N'
CREATE PROCEDURE SomeSPname
AS
BEGIN
'+#sql+'
END'
EXEC sp_executesql #query
USE tempdb
IF OBJECT_ID(N'#temp') IS NOT NULL DROP TABLE #temp
SELECT *
INTO #temp
FROM OPENROWSET(
'SQLNCLI',
'Server=SERVER\INSTANCE;Database=Test;Trusted_Connection=yes;',
'EXEC dbo.SomeSPname')
SELECT *
FROM #temp

sp_cursorprepare 'SELECT ........ WHERE 0 = 1' - what for?

SQL Server Profiler shows that our ERP application sends quite a lot commands to DB server which look like:
declare #p1 int
set #p1=NULL
declare #p5 int
set #p5=16388
declare #p6 int
set #p6=8196
exec sp_cursorprepare #p1 output,NULL,N'SELECT * FROM SomeTable WHERE 0 = 1',1,#p5 output,#p6 output
select #p1, #p5, #p6
To emphasize:
exec sp_cursorprepare and then SELECT (...) WHERE 0 = 1
Does it make any sense? Is this a kind of trick?
When an app generates SQL statements with "WHERE 0=1" or "WHERE 1=1", it's usually being used as placeholder so that the app can dynamically build the rest of the WHERE clause using AND or OR without having to figure out which condition is first and needs to immediately follow "WHERE".
So yes, it's a trick to make building dynamic WHERE clauses easier for the app developer. I've used it myself.

Why would a stored procedure perform differently when executed remotely to locally?

We've a stored procedure that happens to build up some dynamic SQL and execute via a parametrised call to sp_executesql.
Under normal conditions, this works wonderfully, and has made a large benefit in execution times for the procedure (~8 seconds to ~1 second), however, under some unknown conditions, something strange happens, and performance goes completely the other way (~31 seconds), but only when executed via RPC (i.e. a call from a .Net app with the SqlCommand.CommandType of CommandType.StoredProcedure; or as a remote query from a linked server) - if executed as a SQL Batch using SQL Server Management Studio, we do not see the degradation in performance.
Altering the white-space in the generated SQL and recompiling the stored procedure, seems to resolve the issue at least in the short term, but we'd like to understand the cause, or ways to force the execution plans to be rebuilt for the generated SQL; but at the moment, I'm not sure how to proceed with either?
To illustrate, the Stored Procedure, looks a little like:
CREATE PROCEDURE [dbo].[usp_MyObject_Search]
#IsActive AS BIT = NULL,
#IsTemplate AS BIT = NULL
AS
DECLARE #WhereClause NVARCHAR(MAX) = ''
IF #IsActive IS NOT NULL
BEGIN
SET #WhereClause += ' AND (svc.IsActive = #xIsActive) '
END
IF #IsTemplate IS NOT NULL
BEGIN
SET #WhereClause += ' AND (svc.IsTemplate = #xIsTemplate) '
END
DECLARE #Sql NVARCHAR(MAX) = '
SELECT svc.[MyObjectId],
svc.[Name],
svc.[IsActive],
svc.[IsTemplate]
FROM dbo.MyObject svc WITH (NOLOCK)
WHERE 1=1 ' + #WhereClause + '
ORDER BY svc.[Name] Asc'
EXEC sp_executesql #Sql, N'#xIsActive BIT, #xIsTemplate BIT',
#xIsActive = #IsActive, #xIsTemplate = #IsTemplate
With this approach, the query plan will be cached for the permutations of NULL/not-NULL, and we're getting the benefit of cached query plans. What I don't understand is why it would use a different query plan when executed remotely vs. locally after "something happens"; I also don't understand what the "something" might be?
I realise I could move away from parametrisation, but then we'd lose the benefit of caching what are normally good execution plans.
I would suspect parameter sniffing. If you are on SQL Server 2008 you could try including OPTIMIZE FOR UNKNOWN to minimise the chance that when it generates a plan it does so for atypical parameter values.
RE: What I don't understand is why it would use a different query plan when executed remotely vs. locally after "something happens"
When you execute in SSMS it won't use the same bad plan because of different SET options (e.g. SET ARITHABORT ON) so it will compile a new plan that works well for the parameter values you are currently testing.
You can see these plans with
SELECT usecounts, cacheobjtype, objtype, text, query_plan, value as set_options
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
cross APPLY sys.dm_exec_plan_attributes(plan_handle) AS epa
where text like '%FROM dbo.MyObject svc WITH (NOLOCK)%'
and attribute='set_options'
Edit
The following bit is just in response to badbod99's answer
create proc #foo #mode bit, #date datetime
as
declare #Sql nvarchar(max)
if(#mode=1)
set #Sql = 'select top 0 * from sys.objects where create_date < #date /*44FC79BD-2AF5-4774-9674-04D6C3D4B228*/'
else
set #Sql = 'select top 0 * from sys.objects where modify_date < #date /*44FC79BD-2AF5-4774-9674-04D6C3D4B228*/'
EXEC sp_executesql #Sql, N'#date datetime',
#date = #date
go
declare #d datetime
set #d = getdate()
exec #foo 0,#d
exec #foo 1, #d
SELECT usecounts, cacheobjtype, objtype, text, query_plan, value as set_options
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
cross APPLY sys.dm_exec_plan_attributes(plan_handle) AS epa
where text like '%44FC79BD-2AF5-4774-9674-04D6C3D4B228%'
and attribute='set_options'
Returns
Recompilation
Any time the execution of the SP would be significantly different due to conditional statements the execution plan which was cached from the last request may not be optimal for this one.
It's all about when SQL compiles the execution plan for the SP. They key section regarding sp compilation on Microsoft docs is this:
... this optimization occurs automatically the first time a stored procedure is run after SQL Server is restarted. It also occurs if an underlying table that is used by the stored procedure changes. But if a new index is added from which the stored procedure might benefit, optimization does not occur until the next time that the stored procedure is run after SQL Server is restarted. In this situation, it can be useful to force the stored procedure to recompile the next time that it executes
SQL does recompile execution plans at times, from Microsoft docs
SQL Server automatically recompiles stored procedures and triggers when it is advantageous to do this.
... but it will not do this with each call (unless using WITH RECOMPILE), so if each execution could be resulting in different SQL, you may be stuck with the same old plan for at least one call.
RECOMPILE query hint
The RECOMPILE query hint, takes into account your parameter values when checking what needs to be recompiled at the statement level.
WITH RECOMPILE option
WITH RECOMPILE (see section F) will cause the execution plan to be compiled with each call, so you will never have a sub-optimal plan, but you will have the compilation overhead.
Restructure into multiple SPs
Looking at your specific case, the execution plan for the proc never changes and the 2 sql statements should have prepared execution plans.
I would suggest that restructuring the code to split the SPs rather than have this conditional SQL generation would simplify things and ensure you always have the optimal execution plan without any SQL magic sauce.

Resources