Procedure FunctionX, Line 345
Invalid use of a side-effecting operator 'EXECUTE STRING' within a
function.
I get the above error when I execute a dynamic statement inside a function in SQL Server 2012.
Is there a workaround for this? Any tricks?
PS: The sproc (stored procedure) is much too lengthy for its body to be inserted as-is inside the function.
DECLARE #execsql NVARCHAR(2000)
Set #execsql = 'INSERT INTO #TABLE1 EXEC SPROC1 ' + #ID_COMPANY + ',' + #ID_COUNTRY
exec (#execsql)
Many thanks in advance.
Also, I need to be able to delete inside the function as well. I know this contradicts the definition of functions but I am wondering if there are some tricks that can be used
No there are no tricks, see The Curse and Blessings of Dynamic SQL
Dynamic SQL in User-Defined Functions
This is very simple: you cannot use dynamic SQL from used-defined
functions written in T-SQL. This is because you are not permitted to do
anything in a UDF that could change the database state (as the UDF may
be invoked as part of a query). Since you can do anything from dynamic
SQL, including updates, it is obvious why dynamic SQL is not
permitted.
I've seen more than one post on the newsgroups where people have been
banging their head against this. But if you want to use dynamic SQL in
a UDF, back out and redo your design. You have hit a roadblock, and in
SQL 2000 there is no way out.
In SQL 2005 and later, you could implement your function as a CLR
function. Recall that all data access from the CLR is dynamic SQL.
(You are safe-guarded, so that if you perform an update operation from
your function, you will get caught.) A word of warning though: data
access from scalar UDFs can often give performance problems. If you
say
SELECT ... FROM tbl WHERE dbo.MyUdf(somecol) = #value
and MyUdf performs data access, you have more or less created a hidden
cursor.
I was having this same problem with dynamic OPENQUERY statements inside a multi-line table-valued function. SQL Server is trying to prevent users with only db_datareader access, who can select from these functions, from performing SQL injections. Long story short, remove as many single quotes as you can and find a way to do the same thing without using EXEC.
Instead of doing this:
Set #execsql = 'INSERT INTO #TABLE1 EXEC SPROC1 ' + #ID_COMPANY + ',' + #ID_COUNTRY
Do something like this:
INSERT INTO #TABLE1
SELECT *
FROM --some unfiltered version of the table your stored procedure uses
WHERE company = #ID_COMPANY
AND country = #ID_COUNTRY
Since you're calling a function from a stored procedure you can already be sure the table will be up to date. In my case, I was able to have a job refresh the function's underlying table using the stored procedure once every morning. You could also use a trigger to do that.
Related
I have inherited a bunch of stored procedures basically as a shell and inside the quotes is this huge dynamic SQL with lots of conditions, calculations and case statements, however the table name in the FROM clause within this dynamic SQL changes every quarter.
Now before I get flamed, I like to simply say that I inherited them, how it was designed was before me. So each quarter when a call is made out to these stored procedures, it comes with the actual table name passed as a parameter and then the dynamic SQL concatenates the table name.
The problem with this approach is that, with each run over time, the prior designers simply tacked on more criteria as conditions and calculations. But the dynamic SQL string has a length limit to it. Further it becomes quite difficult to maintain and debug.
CREATE PROCEDURE .....
#dynSQL1 = 'SELECT......
FROM' + strTblName + '
WHERE.....
GROUP BY....'
...
EXEC #dynSQL1
GO
However, I like to ask you all, is there a way to turn this stored procedure with this huge dynamic SQL string into a plain vanilla stored procedure based on a parameterized table name?
My main goal is two fold, one, get away from the long string as dynamic SQL and two, easier maintenance and debugging. I would like to think in the more current version of SQL Server from SQL Server 2016/2017 and on, this issue is addressed.
Your thoughts and suggestions is greatly appreciated.
~G
So each quarter when a call is made out to these stored procedures, it comes with the actual table name passed as a parameter and then the dynamic SQL concatenates the table name.
You could change the procedure to codegen other stored procedures instead of running dynamic SQL. EG:
CREATE PROCEDURE admin.RegenerateProcedures #tableName
as
begin
declare #ddl nvarchar(max) = '
create or alter procedure dbo.SomeProc
as
begin
SELECT......
FROM dbo.' + quotename(#tableName) + '
WHERE.....
GROUP BY....
end
'
EXEC ( #ddl )
. . .
end
GO
I am attempting to query data from our Oracle server via our SQL server. To perform this in a thin-client manner, I am using OpenQuery. I would like to build a single table-based function that can be used to query various linked tables as needed. I can't quite figure out the syntax to make this work. Here is what I have so far. Any help is greatly appreciated!
CREATE FUNCTION [dbo].[fnTEST](
#Table varchar (100),
#Fields varchar (1000),
#Condition varchar(5000)
)
RETURNS
#TEST TABLE()
AS
BEGIN
DECLARE #OPENQUERY nvarchar(4000);
DECLARE #TSQL nvarchar(4000);
SET #OPENQUERY = 'SELECT * FROM OPENQUERY([TEST-Link],'''
SET #TSQL = 'SELECT ' + #Fields + ' FROM TEST.' + #Table + ' WHERE ' + #Condition + ''')'
EXEC (#OPENQUERY+#TSQL)
END;
The error I am currently getting is:
Msg 102, Level 15, State 1, Procedure fnTEST, Line 12 [Batch Start Line 7]
Incorrect syntax near ')'.
Highlighted at #TEST TABLE()
This is all not recommended for a number of reasons, but here the big one is that, as indicated in the MS doc, you cannot use dynamic SQL from a user-defined function:
Before You Begin
Limitations and restrictions
...
User-defined functions cannot make use of dynamic SQL or temp tables. Table variables are allowed.
...
Here are some of the other problems with this approach:
Your dynamic SQL is injectable. You should never use dynamic SQL unless you understand what SQL Injection is and how to prevent it in your dynamic SQL code.
Using dynamic sql has potential security requirements and restrictions. In this case the dynamic SQL may not have the same rights as your account and may not be able to use an OPENQUERY.
The nature of Database and Server Trustworthy settings may block this anyway.
IMHO, OPENQUERY is not recommended (some disagree), and remote queries are better handled with Linked Servers and the Remote EXEC command.
You are trying to write a "Universal Query" here. Universal Queries are generally not a good idea and have security problems, even after you fix the SQL Inject issues. It's better to define the specific queries needed by your app and code them as stored procedures and/or fixed queries using parameters only for WHERE conditions.
A SQL Function is not the right place for all of this anyway. You should regard a SQL table function as akin to a View, but with parameters for your WHERE clause. You should not treat it as a way to magically do anything.
The way that I would do something link this is as follows:
Define the explicit queries/datasets that your app needs from the Oracle Database.
Write those queries as stored procedures, on the Oracle database.
Setup a Linked Server definition in your SQL Server database to the Oracle database. Configure the security for each side appropriately.
Write specific stored procedures on your SQL Server to call the corresponding procedures in the Oracle database. Use remote EXEC's to do this through the Linked Server definition.
(NOTE: Remote EXEC execution is done with the AT <linkedServer> clause).
Enable the linked server for rpc out and simplify this to
EXEC (#sql) at [TEST-Link]
I would like to call a stored procedure or user-defined function that returns a dynamic table that is created via a pivot expression. I don't know the number of columns up front.
Is this possible? (I am not interested in temporary tables)
You can do that via stored procedure as it can return any kind of table, question is what are you trying to achieve and what will you do with data that you have no idea about?
This cannot be done with functions (as the returned table structure must be pre-defined), but it can be done with a stored proceed. Some psuedo-code:
CREATE PROCEDURE Foo
As
DECLARE #Command
SET #Command = 'SELECT * from MyTable'
-- For debugging, work in an optional PRINT #Command statement
EXECUTE (#Command)
RETURN 0
When you run stored procedure Foo, it builds your query as a string in #Command, and then dynamically executes it without knowing anything about what is being queried or returned, and the data set returned by that EXECUTE statement is "passed back" to the process that called the procedures.
Build your query with care, this stuff can be really hard to debug. Depending on your implementation, it might be a source of SQL injection attacks (remember, the stored procedure really doesn't know what that dynamic query is going to do). For quick stuff, EXECUTE() works fine, but for safer and more useful (if elaborate) solutions, look into sp_ExecuteSQL.
Yes, you can do this from a Stored Procedure, but not from a user-defined Function. It is worth looking into the Table Value Function, I believe you can also return a dynamic table from there, but I have not used that myself.
I am having the stored procedure. For that i need to pass the Database name as the paramters from another application or another SP. I know the approach of dynamic SQL, something like,
Create procedure mysp(#dbname varchar(20))
as
begin
declare #sql varchar(max)
set #sql='select * from '+#dbname+'.dbo.table'
end
exec mysp 'mydb'
But i dont want the SQL statements as a string. Because in my SP, i have many Sql statements are coming (Not like this only SELECT statement). so can i use,
USE DatabaseName
inside the stored procedure, so that i can use the db name in the sql statements directly without making it as string. Or any other approach is there.
My requirements, only for db name, i dont want the entire the sql statement to be dynamic...
please help me out.
Thanks in advance.
You can add the USE instruction to the dynamic query you are creating. Then you can work with that database's tables and other objects without the qualifier (within the dynamic query):
Create procedure mysp(#dbname varchar(20))
as
begin
declare #sql varchar(max)
set #sql='use '+#dbname;
set #sql=#sql + ';select ... from dbo.table1';
set #sql=#sql + ';update dbo.table2...';
set #sql=#sql + ';insert into dbo.table3...';
...
exec(#sql);
end
exec mysp 'mydb'
However, while you can do that, it's not something that you should do, unless you really have to. You are probably trying to avoid creating the same procedure in different DBs, but you may be getting you other problems with this approach, or robbing yourself of some advantages you might otherwise have without resorting to dynamic queries in SPs.
No, USE isn't allowed in stored procedures, functions and triggers.
A stored procedure is supposed to be local to the database. To access another database, there is one way (as far as I know), and it's the one you used.
I've got a search screen on which the user can specify any combination of first name, last name, semester, or course. I'm not sure how to optimally code the SQL Server 2005 stored procedure to handle these potentially optional parameters. What's the most efficient way? Separate procedures for each combination? Taking the items in as nullable parms and building dynamic SQL?
I'd set each parameter to optional (default value being null)
and then handle it in the WHERE....
FirstName=ISNULL(#FirstName,FirstName)
AND
LastName=ISNULL(#LastName,LastName)
AND
SemesterID=ISNULL(#SemesterID,SemesterID)
That'll handle only first name, only last name, all three, etc., etc.
It's also a lot more pretty/manageable/robust than building the SQL string dynamically and executing that.
The best solution is to utilize sp_execute_sql. For example:
--BEGIN SQL
declare #sql nvarchar(4000)
set #sql =
'select * from weblogs.dbo.vwlogs
where Log_time between #BeginDate and #EndDate'
+ case when #UserName is null then '' else 'and client_user = #UserName' end
sp_execute_sql
#sql
, #params = '#UserName varchar(50)'
, #UserName = #UserName
--END SQL
As muerte mentioned, this will have a performance benefit versus exec()'ing a similar statement.
I would do it with sp_executesql because the plan will be cached just for the first pattern, or the first set of conditions.
Take a look at this TechNet article:
sp_executesql can be used instead of stored procedures to execute a Transact-SQL statement many times when the change in parameter values to the statement is the only variation. Because the Transact-SQL statement itself remains constant and only the parameter values change, the SQL Server query optimizer is likely to reuse the execution plan it generates for the first execution.
Was just posting the same concept as Kevin Fairchild, that is how we typically handle it.
You could do dynamic sql in your code to create the statement as required but if so you need to watch for sql injection.
As muerte points out, the plan will be cached for the first set of parameters. This can lead to bad performance when its run each subsequent time using alternate parameters. To resolve that use the WITH RECOMPILE option on the procedure.