SET FMTONLY OFF effects on stored procedures with dynamic query - sql-server

I use Telerik Report designer R1 2017, and use a stored procedure with dynamic query as data source. It appears the schema cannot be obtained from the dynamic query. I Googled and found out that it's not possible without adding the following lines in the beginning of the stored procedure
IF 1 = 0 BEGIN
SET FMTONLY OFF
END
But I have really slow execution in this way! I'm not very familiar with store procedure execution process. I want to know What negative effects can it (SET FMTONLY OFF) have?

If you are using SQL Server 2012+ you could use WITH RESULT SETS to define resultset:
CREATE OR ALTER PROCEDURE dbo.mysp_test
AS
BEGIN
DECLARE #sql NVARCHAR(MAX) = 'SELECT id, b FROM dbo.tab ' + 'WHERE 1=1';
EXEC sp_executesql #sql
END
GO
SELECT *
FROM sys.dm_exec_describe_first_result_set (
'EXEC dbo.mysp_test'
,NULL
,NULL
);
The metadata could not be determined because statement 'EXEC sp_executesql #sql' in procedure 'mysp_test' contains dynamic SQL.
Using WITH RESULT SETS:
CREATE OR ALTER PROCEDURE dbo.mysp_test
AS
BEGIN
DECLARE #sql NVARCHAR(MAX) = 'SELECT id, b FROM dbo.tab ' + 'WHERE 1=1';
EXEC sp_executesql #sql
WITH RESULT SETS(
(id INT NOT NULL,
b CHAR(1)
)
);
END
GO
SELECT *
FROM sys.dm_exec_describe_first_result_set (
'EXEC dbo.mysp_test'
,NULL
,NULL
);
DBFiddle Demo

Related

Need help. Message says temp table doesn't exist: 'Invalid object name '#TempCodes'.'

If I run the select statement below by itself (with the table name hard coded in) it runs fine and the temp table is created. If I run it as below it says 'Invalid object name '#TempCodes'' although when I print #Sql1 it looks exactly the same as when I run it by itself (with the table name hard coded in).
Any ideas on what's going on here will be appreciated.
DECLARE #TableName NVARCHAR(50)
SET #TableName = '[My Table Name]'
DECLARE #Sql1 NVARCHAR(MAX);
SET #Sql1 = N'SELECT AccountNumber,LTRIM(RTRIM(m.n.value(''.[1]'',''varchar(8000)''))) AS mdcodes INTO #TempCodes FROM (SELECT AccountNumber,CAST(''<XMLRoot><RowData>''
+ REPLACE(MD_Results,'','',''</RowData><RowData>'')
+ ''</RowData></XMLRoot>'' AS XML) AS x FROM ' + #TableName
+ N')t CROSS APPLY x.nodes(''/XMLRoot/RowData'')m(n)'
IF OBJECT_ID('tempdb.dbo.#TempCodes', 'U') IS NOT NULL
BEGIN
drop table #TempCodes
END
EXECUTE sp_executesql #Sql1
Select * from #TempCodes
I believe ## is a typo. Even if you fix it to # it will throw same error.
EXECUTE sp_executesql #Sql1
A local temporary table created in a stored procedure is dropped
automatically when the stored procedure is finished.
In your case sp_executesql is the stored procedure.
the table created inside the dynamic query will be dropped after the exec sp_executesql is completed. That's why you are getting that error.
You need to create table outside and use Insert into table..select syntax inside the dynamic query
IF OBJECT_ID('tempdb.dbo.#TempCodes', 'U') IS NOT NULL
drop table #TempCodes
create table #TempCodes(AccountNumber varchar(100),mdcodes varchar(100))
SET #Sql1 = N'Insert into #TempCodes
SELECT AccountNumber,LTRIM(RTRIM(m.n.value(''.
[1]'',''varchar(8000)''))) AS mdcodes FROM (SELECT
AccountNumber,CAST(''<XMLRoot><RowData>'' +
REPLACE(MD_Results,'','',''</RowData><RowData>'') + ''</RowData></XMLRoot>''
AS XML) AS x FROM '+#TableName+ N')t CROSS APPLY
x.nodes(''/XMLRoot/RowData'')m(n)'
Try using a global temp table ##TempCodes as local temporary tables are only visible in current session.
DECLARE #TableName NVARCHAR(50)
SET #TableName = '[My Table Name]'
DECLARE #Sql1 NVARCHAR(MAX);
SET #Sql1 = N'SELECT AccountNumber,LTRIM(RTRIM(m.n.value(''.
[1]'',''varchar(8000)''))) AS mdcodes INTO ##TempCodes FROM (SELECT
AccountNumber,CAST(''<XMLRoot><RowData>'' +
REPLACE(MD_Results,'','',''</RowData><RowData>'') + ''</RowData></XMLRoot>''
AS XML) AS x FROM '+#TableName+ N')t CROSS APPLY
x.nodes(''/XMLRoot/RowData'')m(n)'
IF OBJECT_ID('tempdb.dbo.##TempCodes', 'U') IS NOT NULL
BEGIN
drop table ##TempCodes
END
EXECUTE sp_executesql #Sql1
Select * from ##TempCodes

Call SQL stored proc with OUTPUT param from another stored proc

It seems very simple solution, but I can't figure it out. Please help.
I have to call a stored proc with OUTPUT param from another stored proc. I think one of the issues is dynamic SQL, but I don't know how else to write it since #SQLWhere will change dynamically within C# code.
This is the proc being called from another proc:
ALTER PROCEDURE [dbo].[USP_RetrieveTotalRecord]
#SQLWhere AS NVARCHAR(1000),
#TotalRecordsFound as varchar(16) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL AS NVARCHAR(Max)
SET #SQL='Select #TotalRecordsFound = Count(Table_ID)
From TableName Where ' + #SQLWhere
EXEC(#SQL)
return
END
Here is how I am calling it from another proc:
Declare #TotalRec AS NVARCHAR(16);
Declare #SQLWhere AS NVARCHAR(1000);
SET #SQLWhere='Date Between ''12/13/2016'' AND ''12/14/2016'''
EXECUTE USP_RetrieveTotalRecord #SQLWhere, #TotalRec output;
Here is the error I am trying to resolve:
Msg 137, Level 15, State 1, Line 30
Must declare the scalar variable "#TotalRecordsFound".
Don't do what you are trying to do, Only pass values to stored procedure and then build the dynamic sql inside your procedure, something like ......
ALTER PROCEDURE [dbo].[USP_RetrieveTotalRecord]
#StartDate DATE = NULL
,#EndDate DATE = NULL
,#TotalRecordsFound INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(Max);
SET #SQL = N' Select #TotalRecordsFound = Count(Table_ID) '
+ N' From TableName Where 1 =1 '
+ CASE WHEN #StartDate IS NOT NULL THEN
N' AND [Date] >= #StartDate ' ELSE N' ' END
+ CASE WHEN #EndDate IS NOT NULL THEN
N' AND [Date] <= #EndDate ' ELSE N' ' END
EXEC sp_executesql #SQL
,N'#StartDate DATE, #EndDate DATE, #TotalRecordsFound INT OUTPUT'
,#StartDate
,#EndDate
,#TotalRecordsFound OUTPUT
END
Now #EndDate and #StartDate variables are optional , depending on what variable values you pass procedure will build the dynamic sql accordingly and return the results.
Also using parameterised query with sp_executesql will protect you against a possible SQL-Injection attach and also your proc will benefit from parameterised execution plans.
Not sure why you have a 2nd SP, just use one like so:
Declare #TotalRec AS NVARCHAR(16);
Declare #SQLWhere AS NVARCHAR(1000);
SET #SQLWhere='Date Between ''12/13/2016'' AND ''12/14/2016'''
SET #SQL='Select #TotalRecordsFound = Count(Table_ID)
From TableName Where ' + #SQLWhere
EXEC(#SQL)
Or use date variables if that's all you are using for selection (no dynamic sql necessary) - unless this is just a simplified example
--- comment section is broken, so, in response to get a value out, use something like this:
Ok - the simplest way is to use sp_ExecuteSQL
Declare #result int
Declare #sql nvarchar(max)
SET #SQL = ' SELECT COUNT(*) FROM MyTable'
exec sp_ExecuteSQL #sql, N' #Result int Output', #Result output
select #result as MyCount
M. ali, thanks for your help, but we have all SELECT, WHERE, and GROUP by criteria being passed from the application after dynamic selections. I needed a quick fix.
Finally I was able to resolve the issue using a temp table. I know they are not recommended, but I tried using Common table expression, table variable, but the #TotalRecordsFound was not visible outside the dynamic SQL. Therefore, created temp table, Inserted data into it using dynamic SQL, and then joined it with the next select statement.

Variable table name in select statement

I have some tables for storing different file information, like thumbs, images, datasheets, ...
I'm writing a stored procedure to retrieve filename of a specific ID. something like:
CREATE PROCEDURE get_file_name(
#id int,
#table nvarchar(50)
)as
if #table='images'
select [filename] from images
where id = #id
if #table='icons'
select [filename] from icons
where id = #id
....
How can I rewrite this procedure using case when statement or should I just use table name as variable?
You can't use case .. when to switch between a table in the FROM clause (like you can in a conditional ORDER BY). i.e. so the following:
select * from
case when 1=1
then t1
else t2
end;
won't work.
So you'll need to use dynamic SQL. It's best to parameterize the query as far as possible, for example the #id value can be parameterized:
-- Validate #table is E ['images', 'icons', ... other valid names here]
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'select [filename] from **TABLE** where id = #id';
SET #sql = REPLACE(#sql, '**TABLE**', #table);
sp_executesql #sql, N'#id INT', #id = #id;
As with all dynamic Sql, note that unparameterized values which are substituted into the query (like #table), make the query vulnerable to Sql Injection attacks. As a result, I would suggest that you ensure that #table comes from a trusted source, or better still, the value of #table is compared to a white list of permissable tables prior to execution of the query.
Just build SQL string in another variable and EXECUTE it
DECLARE #sql AS NCHAR(500)
SET #sql=
'SELECT [filename] '+
' FROM '+#table+
' WHERE id = #id'
EXECUTE(#sql)
CREATE PROCEDURE get_file_name(
#id int,
#table nvarchar(50)
)as
DECLARE #SQL nvarchar(max);
SET #SQL = 'select [filename] from ' + #table + ' where id = ' + #id
EXECUTE (#SQL)

Using temp table with exec #sql in stored procedure

I have a stored procedure and part of them as below:
#DRange is a incoming varchar value
declare #sql varchar(max)
set #sql = 'select * into #tmpA from TableA where create_date >= getDate - ' + #DRange + '' and is_enabled = 1'
exec (#sql)
select * from #tmpA
The problem is when I execute the stored procedure, an error message occurs:
Cannot find the object "#tmpA" because it does not exist or you do not have permissions.
Is it not possible to use temp table and execute it or did I do something wrong?
#tmpA is created in a different scope, so is not visible outside of the dynamic SQL. You can just make the ultimate SELECT a part of the dynamic SQL. Also a couple of other things:
Always use the schema prefix when creating/referencing objects
Always use sp_executesql for dynamic SQL; in this case it allows you to parameterize the #DRange value and avoid SQL injection risks.
Always prefix Unicode strings with N - Unicode is required for sp_executesql but if you get lazy about this in other areas of your code it can also lead to painful implicit conversions.
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'select * into #tmpA from dbo.TableA
where create_date >= DATEADD(DAY, -#DRange, GETDATE())
AND is_enabled = 1; SELECT * FROM #tmpA';
EXEC sp_executesql #sql, N'#DRange INT', #DRange;
Of course if all you're doing is selecting, I have a hard time understanding why this is dynamic SQL in the first place. I assume your query (or what you later do with the temp table) is more complicated than this - if so, don't dumb it down for us. Telling us your whole problem will prevent a lot of back and forth, as the additional details could change the answer.
Here's what I'd do.
declare #sql varchar(max)
set #sql = 'select * from TableA where create_date >= getDate - ' + #DRange + '' and is_enabled = 1'
Select * Into #tmpA from TableA where create_date = '01/01/1000' -- to create a blank table
insert into #tmpA
exec (#sql)
select * from #tmpA

Stored Procedure and populating a Temp table from a linked Stored Procedure with parameters

I have a Stored Procedure (SP) in which I pass in one value. In this SP, I am trying to create/populate a Temp Table from the result of another SP that is on a Linked/remote server. That is I am trying to executute an SP in my SP and populate a temp table which my query will use.
I have tried using the following syntax, but it does not work as it seems openquery does not like the "+" or the #param1 parameter.
select * into #tempTable
from openquery([the Linked server],'exec thelinkedSPname ' + #param1)
If I have the parameter value hard coded in this it works fine.
select * into #tempTable
from openquery([the Linked server],'exec thelinkedSPname 2011')
I have also gone as far as manually building the temp table and trying to execute the linked SP but that does not work as well.
create table #tempTable(
.
.
.
)
insert into #tempTable
(
.
.
.
)
Exec [the Linked server],'exec thelinkedSPname ' + #param1
Any suggestions as to how to populate a temp table from within a SP that executes a SP via a linked server. Note the above SQL is only pseudo code
I think you are gonna need dynamic SQL, since you can't pass the parameter to an OPENQUERY like that (but first visit this link) So you would have something like this:
create table #tempTable(
.
)
DECLARE #param1 VARCHAR(10), #Query VARCHAR(8000)
SET #param1 = '2011'
SET #Query = '
SELECT *
FROM OPENQUERY([Linked Server],''exec thelinkedSPname '' + #param1+''')'
INSERT INTO #tempTable
EXEC(#Query)
With the usual disclaimers about guarding dynamic SQL, you can do this without OPENQUERY etc. Just call sp_executesql remotely:
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'EXEC thelinkedSPname ' + #param1 + ';';
INSERT #temptable EXEC [LinkedServerName].database.dbo.sp_executesql #sql;
I use this method quite frequently:
DECLARE #YEAR AS VARCHAR(4) SET #YEAR = 2015
DECLARE #SQL AS VARCHAR(MAX)
DECLARE #OPENQUERY AS VARCHAR(MAX)
DECLARE #LINKEDSERVER AS VARCHAR(MAX) SET #LINKEDSERVER = 'Name of Linked Server here with out brackets'
SET #SQL='
Select
tbl1.*
FROM
dbo.Table_ON_LINKED_SERVER AS tbl1
WHERE
tbl1.number_id = ''''1''''
AND YEAR(tbl1.DATETIME) = ' + #YEAR + '
AND tbl1.NAME <> ''''%JONES%''''
'''
SET #OPENQUERY = 'SELECT * INTO ##GLOBAL_TEMP_NAME FROM OPENQUERY(['+ #LINKEDSERVER +'],''' + #SQL + ')'
--SELECT #OPENQUERY
EXEC(#OPENQUERY)
Two words: Dynamic Query.
Try this:
DECLARE #TSQL varchar(8000)
SELECT #TSQL = 'SELECT * INTO #tempTable FROM OPENQUERY([the Linked server],''exec [the Linked server].DBName.dbo.thelinkedSPname ' + #param1 + ''')'
EXEC (#TSQL)
This is well documented here:
How to pass a variable to a linked server query
With some care you could use a shared temp table:
DECLARE #Qry AS VARCHAR(MAX)
SET #Qry = 'select * into ##tempTable from openquery([the Linked server],''exec thelinkedSPname ' + #param1 + ''')'
EXEC (#Qry)
-- Now just use the shared Temp table, or I suppose you could copy it to a temp table just as you wanted it:
SELECT * INTO #tempTable FROM( SELECT * FROM ##tempTable)tbl
DROP TABLE ##tempTable

Resources