SQL Server - Creating a Plan Guide without using an alias - sql-server

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.

Related

SQL Server Syntax failure

I am running the following code and I am getting a syntax error near '.TableReference' error, the code use to work then I did something and now I have this error and I can't seem to find the issue
Through troubleshooting I have narrowed the code issue to the ' FROM ' + #TableName section but it appears to be good code.
BEGIN
--SET NOCOUNT ON;
DECLARE #TableName AS NVARCHAR(MAX) --The Fully qualified database name
DECLARE #Ref AS NVARCHAR(MAX) --The name of the Table we are processing
DECLARE #TempTab AS NVARCHAR(MAX) --the temporary table we are subjecting to the tortures of this process
DECLARE #TempQuery AS NVARCHAR(MAX) --Query to move all data into the temporary table
--This selects the first record in the Website Request Table which hasn't been processed and passes it into the TempTab variable
SET #NDTRef = (SELECT TOP 1 Reference from dbo.WebRequestTable Where Processing IS NULL)
SET #TableName = 'Processing.dbo.'+#NDTRef
Set #TempTab = 'TEMP' + #NDTRef
SET #TempQuery = 'SELECT * INTO '+ #TempTab +' FROM ' + #TableName
EXEC sp_sqlexec #TempQuery;
END
Any help would be appreciated it is a stand alone instance of SQL Server 2019 and the code is a part of a stored procedure but the rest of the code runs off the temporary table created in this block
After suggestions I put in a print statement regarding the #TempQuery when put straight after and the EXEC removed the output is
SELECT * INTO TEMP2294690 FROM Processing.dbo.2294690
With the EXEC back in play I get the error
Msg 102, Level 15, State 1, Line 17 Incorrect syntax near '.2294690'.
The print output after the EXEC shows:
SELECT * INTO TEMP2294690 FROM Processing.dbo.2294690
The Table 2294690 exists in the database Processing the Temp2294690 is a table that should be created by this block but it isn't being created
In SQL Server, regular Identifiers must begin with a letter, an underscore (_), at sign (#) or the number sigh (#).
(There are other rules as well, but this is the one relevant to the question...)
Identifiers that don't follow the rules of regular identifiers can be only used if they are enclosed in square brackets ([]) or double quotation marks (").
The best way to handle identifiers when creating dynamic SQL statements is to use the built in QUOTENAME function - this way you can make sure your query doesn't break even if the identifier doesn't follow the rules of regular identifiers.
So your SQL should look like this:
SET #TableName = '[Processing].[dbo].'+ QUOTENAME(#NDTRef)
SET #TempTab = 'TEMP' + #NDTRef
SET #TempQuery = 'SELECT * INTO '+ QUOTENAME(#TempTab) +' FROM ' + #TableName
That being said, you should also probably check my blog post entitled The do’s and don’ts of dynamic SQL for SQL Server where you can find some more information about how to safely create dynamic SQL.
You know that if the view is temporary only you can see it and if you close your session user the view is deleted and dont save try with create view dont temp and drop after you read.
If all previus you have it in your mind you could try this for see if the view has created --> its a extract of Microsoft official pagge:
VIEW_METADATA
Specifying the instance of SQL Server will return the DB-Library, ODBC, and OLE DB APIs the metadata information about the view instead of the base tables when you request the browse mode metadata for a query that references the view. Browse mode metadata is additional metadata that the SQL Server instance returns to these client-side APIs. This metadata enables client-side APIs to implement updateable client-side cursors. Browse mode metadata includes information about the base table to which the columns in the result set belong.

creating a plan guide

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?

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

Use the result of a system stored procedure as a queryable table

Note: the highest linked question does not solve the problem for system stored procedures, but it's close. With help of the commenters, I came to a working answer.
Trying to use statements such as the following for sp_spaceused, throws an error
SELECT * INTO #tblOutput exec sp_spaceused 'Account'
SELECT * FROM #tblOutput
The errors:
Must specify table to select from.
and:
An object or column name is missing or empty. For SELECT INTO statements, verify each column has a name. For other statements, look for empty alias names. Aliases defined as "" or [] are not allowed. Change the alias to a valid name.
When I fully declare a table variable, it works as expected, so it seems to me that the stored procedure does return an actual table.
CREATE TABLE #tblOutput (
name NVARCHAR(128) NOT NULL,
rows CHAR(11) NOT NULL,
reserved VARCHAR(18) NOT NULL,
data VARCHAR(18) NOT NULL,
index_size VARCHAR(18) NOT NULL,
unused VARCHAR(18) NOT NULL)
INSERT INTO #tblOutput exec sp_spaceused 'Response'
SELECT * FROM #tblOutput
Why is it not possible to use a temp table or table variable with the result set of EXECUTE sp_xxx? Or: does a more compact expression exist than having to predefine the full table each time?
(incidentally, and off-topic, Googling for the exact term SELECT * INTO #tmp exec sp_spaceused at the time of writing, returned exactly one result)
TL;DR: use SET FMTONLY OFF with OPENQUERY, details below.
It appears that the link provided by Daniel E. is only part of the solution. For instance, if you try:
-- no need to use sp_addlinkedserver
-- must fully specify sp_, because default db is master
SELECT * FROM OPENQUERY(
[SERVERNAME\SQL2008],
'exec somedb.dbo.sp_spaceused ''Account''')
you will receive the following error:
The OLE DB provider "SQLNCLI10" for linked server "LOCALSERVER\SQL2008" supplied inconsistent metadata for a column. The name was changed at execution time.
I found the solution through this post, and then a blog-post on OPENQUERY, which in turn told me that until SQL2008, you need to use SET FMTONLY OFF. The final solution, which is essentially surprisingly simple (and easier to accomplish since there is no need to specify a loopback linked server), is this:
SELECT * FROM OPENQUERY(
[SERVERNAME\SQL2008],
'SET FMTONLY OFF
EXEC somedb.dbo.sp_spaceused ''Account''')
In addition, if you haven't set DATA-ACCESS, you may get the following error:
Server 'SERVERNAME\SQL2008' is not configured for DATA ACCESS.
This can be remedied by running the following command:
EXEC sp_serveroption 'SERVERNAME\SQL2008', 'DATA ACCESS', TRUE
We cannot SELECT from a stored procedure thats why SELECT * INTO ..Exec sp_ will not work.
To get the result set returned from a store procedure we can INSERT INTO a table.
SELECT INTO statement creates a table on fly and inserts data from the source table/View/Function. The only condition is source table should exist and you should be able to Select from it.
Sql Server doesn't allow you to use SELECT from sp_ therefore you can only use the INSERT INTO statement when executing a stored procedure this means at run time you can add the returned result set into a table and Select from that table at later stage.
INSERT INTO statement requires the destination table name, An existing table. Therefore whether you use a Temp Table, Table variable or Sql server persistent table you will need to create the table first and only they you can use the syntax
INSERT INTO #TempTable
EXECUTE sp_Proc
Using [YOUR DATABASE NAME]
CREATE TABLE [YOURTABLENAME]
(Database_Name Varchar(128),
DataBase_Size VarChar(128),
unallocated_Space Varchar(128),
reserved Varchar(128),
data Varchar(128),
index_size Varchar(128),
unused Varchar(128)
);
INSERT INTO dbo.[YOUR TABLE NAME]
(
Database_Name,
DataBase_Size,
unallocated_Space,
reserved,
data,
index_size,
unused
)
EXEC sp_spaceused #oneresultset = 1
--To get it to return it all as one data set add the nonresultset=1 at the end and viola good to go for writing to a table. :)

How to create dynamic stored procedure in SQL Anywhere?

I'm having an issue with creating dynamic sql statement in SQL Anywhere.
CREATE PROCEDURE pplAnalysis
AS
BEGIN
DECLARE #Sql NVARCHAR(4000)
SELECT #Sql = "select * from cds.ppl"
EXECUTE(#Sql)
END
When I execute this procedure, I get an Column 'select * from cds.ppl' not found error.
Can you please tell me what am I doing wrong?
The issue had to do with syntax and the RESULT clause; after adding semicolons, RESULT clause, and used SET to initialize the Sql variable, here is what worked (tested in SQL Anywhere Network Server Version 12.0.1):
drop proc pplAnalysis;
CREATE PROCEDURE pplAnalysis()
RESULT (cnot CHAR(5), cnull CHAR(5), vnot VARCHAR(5), vnull VARCHAR(5), explanation VARCHAR(50))
BEGIN
DECLARE #Sql NVARCHAR(4000);
SET #Sql = 'select cnot, cnull, vnot, vnull, explanation from dbo.spaces';
EXECUTE ( #Sql );
END;
spaces is a table in the dbo schema and those columns are the same type specified in RESULT
Tested these two ways to execute the procedure and both returned result:
call pplAnalysis();
cnot cnull vnot vnull explanation
----- ----- ----- ----- --------------------------------------------------
Execution time: 0.027 seconds
Procedure completed
or
exec pplAnalysis;
cnot cnull vnot vnull explanation
----- ----- ----- ----- --------------------------------------------------
Execution time: 0.018 seconds
For more details:
Returning result sets from procedures
Create procedure statement
Try first saving the result of the query in a temporal table, and then do a SELECT from the temporal table:
SELECT #Sql = "select into #temp * from cds.ppl"
EXECUTE(#Sql)
SELECT * FROM #temp
Use single quotes.
SELECT #Sql = 'select * from cds.ppl'
After some research, I have edited my answer.
Regarding the EXECUTE ( string-expression ) statement, yes you have to use single quotes instead of double quotes for the string expression. This page mentions:
It lets you execute dynamically prepared statements, such as
statements that are constructed using the parameters passed in to a
procedure. Literal strings in the statement must be enclosed in single
quotes, and the statement must be on a single line.
Which will eliminate the column not found error but the procedure will return this other error:
Result set not permitted in '<batch statement>'
Same error returned when trying to execute this statement alone:
execute ('select * from sysusers')
With probable cause:
You attempted to execute a SELECT statement in a context where a
result set is not permitted.
See my most recent answer for the solution.
And regarding schemas, here's how to refer to objects:
It is always good practice to refer to database objects by a schema
name and the object name, separated by a period (.). For a complete example, to SELECT records from the Employee table in the HumanResources schema of the current database would look like:
SELECT * FROM HumanResources.Employee
To reference an object located in a remote database, the fully
qualified object name includes the server name and the database name.
For example, to SELECT records from the Employee table in the
HumanResources schema in the AdventureWorks database on MyServer would
look like:
SELECT * FROM MyServer.AdventureWorks.HumanResources.Employee
I tested that in SQL Anywhere 12 and it works the same. And even though I was not familiar with schemas, what I'm suggesting you below is actually using schemas, dbowner would be the schema name:
1) select * from dbname.dbowner.tablename
2) select * from dbowner.tablename
3) select * from dbname..tablename (assumes table exists in the dbo schema)
Bottom line.... In your select statement cds.ppl has to be a table named ppl created in the cds schema.
Or if cds is your database name and ppl your table name created in the dbo schema, you are missing a dot:
select * from cds..ppl

Resources