i am trying to execute this scalar function and i tried a lot of approaches to achieve this but i get stuck
Create FUNCTION CalculateElementFunc()
RETURNS int
AS
BEGIN
DECLARE #ResultVar numeric(18,6)
DECLARE #eq nvarchar(MAX)
set #eq = '7.5/100*1258.236'
declare #expression nvarchar(max)
set #expression = #eq
declare #result int
declare #SQLString nvarchar(max)
Set #SQLString = N'Select #result = #expression'
exec sp_executesql #SQLString, N'#expression nvarchar(100)',
#expression,
#result = #result output
select #ResultVar = #result
if( #ResultVar <> ROUND( #ResultVar, 2 ,1))
set #ResultVar = cast( ROUND( #ResultVar, 2 ,1) + .01 as numeric(18,2))
RETURN #ResultVar
END
When i try to execute it
select dbo.CalculateElementFunc()
i get this error
Msg 557, Level 16, State 2, Line 1
Only functions and some extended stored procedures can be executed from within a function.
Please Advice
What you want to do is not recommended in SQL Server. First, it is really hard. As you have learned, a SQL Server function cannot execute dynamic SQL.
This is subtly in the documentation:
EXECUTE statements calling extended stored procedures.
exec and sp_executesql are not extended stored procedures.
What can you do? Here are some options:
Is a stored procedure instead of a UDF a possibility? Stored procedures can execute the dynamic SQL.
Can you get around the problem of expression evaluation? Perhaps dynamic SQL can be used one level up in your code.
You can execute an extended stored procedure that starts another transaction and executes the dynamic SQL. Think: really bad performance.
You can write a CLR extended function.
Limitations on SQL User Defined Functions:
Non-deterministic build in functions cannot be used in user defined functions. e.g. GETDATE() or RAND().
XML data type is not supported.
Dynamic SQL queries are not allowed.
User defined functions does not support any DML statements (INSERT, UPDATE, DELETE) unless it is performed on Table Variable.
We cannot make a call to the stored procedure. Only extended stored procedure can be called from function.
We cannot create Temporary tables inside UDFs.
It does not support Error Handling inside UDF. Although, we can handle errors (RAISEERROR, TRY-CATCH) for the statements which uses this function.
And it looks like you are using/calling a stored procedure inside your User Defined Function. It is not the expression that's bugging you, it's that stored procedure call.
Try to replace it with some logic to achieve your desired output.
Hope this is helpful. If it helps to solve your problem then don't forget to mark it as an answer.
Related
I have the following SQL code:
DECLARE #i INT = 1;
DECLARE #sql_code varchar(max) = '';
DECLARE #repeats INT = 4;
WHILE #i <= #repeats
BEGIN
SET #sql_code = #sql_code+'SELECT ''foo'+cast(#i as varchar)+''' as bar UNION ALL '
SET #i = #i + 1
END;
SET #sql_code = LEFT(#sql_code,LEN(#sql_code) - 10)
exec (#sql_code)
,which when run in SSMS produces this:
bar
----
foo1
foo2
foo3
foo4
How can I reproduce the same result as view (dynamically)?
I know you can't use declarations in view, but could it be done through stored procedure or function?
You can't use dynamic sql inside a view. But yes you can create table valued User-Defined functions as mentioned in this post.
Link to the post: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/3cdeda6c-af19-46e9-b89f-e575fecd475b/dynamic-query-in-view?forum=transactsql
Answer by Gavin Campbell should give you the idea of what can be done.
Note : For more information on Table valued User-Defined Functions: Visit this documentation:
https://learn.microsoft.com/en-us/previous-versions/sql/sql-server-2008-r2/ms191165(v=sql.105)?redirectedfrom=MSDN
Actually, despite what Utsav's good answer says, you can do anything if you put your mind to it. 😉
While this is generally not recommended (this answer is for informational purposes), there are certain use cases where it makes sense to use OPENQUERY() inside a View. OPENQUERY() allows you to execute raw SQL against a remote or local SQL Server. Either in the raw SQL itself, or probably more organized in a stored procedure, there's essentially no limitations on the queries you can run, including dynamic SQL.
Example:
CREATE PROCEDURE dbo.RunSomeDynamicSQL
AS
DECLARE #DynamicSQL NVARCHAR(MAX) =
'
SELECT 1 AS Foobar;
';
EXEC sp_executesql #DynamicSQL;
GO
CREATE VIEW dbo.SomeViewThatExecutesDynamicSQL
AS
SELECT Foobar
FROM OPENQUERY
(
LocalServerName,
'
EXEC YourDatabaseName.dbo.RunSomeDynamicSQL
WITH RESULT SETS
((
Foobar INT
));
'
);
GO
SELECT Foobar
FROM dbo.SomeViewThatExecutesDynamicSQL;
You'll notice I'm using the WITH RESULT SETS keyword when executing my procedure inside of OPENQUERY(). This is because OPENQUERY() needs to know the shape of the result set from the executing query. This is one way to describe that when executing a procedure.
One use case for using OPENQUERY() in a View is so you can maximize your ability to performance tune your query (e.g. inside a stored procedure) without losing consumability of the database object.
One important fact about using OPENQUERY() is that the SQL Server Engine always estimates the cardinality of the results to be 10,000 rows. This means if your result set is much larger than 10,000 rows, for example 10 million rows, then you may not get the most optimal execution plan to serve your query.
Also, despite my informational answer, you can of course use a stored procedure alone if that's sufficient for your use case.
could it be done through stored procedure
Sure, just wrap a stored procedure around your code:
CREATE PROCEDURE dbo.SomeStoredProcedure
AS
DECLARE #i INT = 1;
DECLARE #sql_code NVARCHAR(MAX) = '';
DECLARE #repeats INT = 4;
WHILE #i <= #repeats
BEGIN
SET #sql_code = #sql_code+'SELECT ''foo'+cast(#i as varchar)+''' as bar UNION ALL '
SET #i = #i + 1
END;
SET #sql_code = LEFT(#sql_code,LEN(#sql_code) - 10)
EXEC sp_executesql #sql_code
Note I changed the last line of your code to use sp_executesql because it minimizes your risk for SQL injection issues. You should always use that procedure for dynamic SQL execution instead of directly executing your SQL string.
this function should accept the viewname and return the date on the last record of the view. Can someone tell me what am I missing here?
I call the function in a stored procedure and got this error:
Only functions and some extended stored procedures can be executed from within a function.
My query:
ALTER FUNCTION [dbo].[udf_GetLastDate] (#ViewName nvarchar(4000))
RETURNS date
AS
BEGIN
DECLARE #SQLCommand nvarchar(4000);
DECLARE #LastTransDate date;
SET #SQLCommand = 'SELECT #LastTransDate=LAST(TRANSDATE) FROM' + #ViewName;
EXECUTE sp_executesql #sqlCommand
RETURN #LastTransDate;
end
The problem is you are trying to execute Dynamic-SQL from function. You simply cannot do it. Period.
Dynamic SQL in User-Defined Functions:
This very simple: you cannot use dynamic SQL from used-defined functions written in T-SQL. This is because you are not permitted 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.
Consider changing function to stored procedure:
CREATE PROCEDURE [dbo].[mysp_GetLastDate]
#ViewName SYSNAME
AS
BEGIN
DECLARE #SQLCommand NVARCHAR(MAX) =
N'SELECT MAX(TRANSDATE) FROM' + QUOTENAME(#ViewName);
EXECUTE [dbo].[sp_executesql]
#sqlCommand;
END
Also keep in mind that SQL Server does not have LAST function. If you need newest TRANSDATE use MAX() or SELECT TOP 1 TRANSADATE FROM ... ORDER BY some_column DESC
I have a procedure that accepts multiple varchar(4000) params (26 of them).
Each of them is a comma-separated string of values.
Once they are passed in, I would like to break each of the strings apart and insert them into a temp table for use later in the proc.
I'd prefer not to write a statement that processes each parameter individually, but rather write a while loop that relies on a counter to loop over each parameter and process each one in turn. Currently, I've tried the following, but its not correct.
CREATE PROCEDURE [dbo].[myproc] (
#string1 varchar(4000) = null;
#string2 varchar(4000) = null;
#string3 varchar(4000) = null;
....declare #string4 -> #string25...
#string26 varchar(4000) = null;)
CREATE TABLE #emails (
address varchar(80)
)
Set #counter = 1
WHILE #counter < 27
BEGIN
INSERT INTO #emails(address) SELECT element as address from FT_SPLIT_LIST(isNull('#string'+convert(varchar,#counter),''),',')
SET #counter = #counter +1
END
SELECT * FROM #emails
Currently, this is not returning a table with all the CSVs from #string1 -> #string26.
FT_SPLIT_LIST works- I use it in many other places. I just need to know if there is a way to dynamically declare the parameter that is being passed in to it?
Is there any way to do what I'm trying to accomplish without writing a statement for each of the #string1->#string27 parameters?
Thanks,
C
SQL Server 2008 and above have table valued parameters:
Table-valued parameters are declared by using user-defined table types. You can use table-valued parameters to send multiple rows of data to a Transact-SQL statement or a routine, such as a stored procedure or function, without creating a temporary table or many parameters.
These are a much better option than comma delimited varchars and FT_SPLIT_LIST.
I suggest reading Arrays and Lists in SQL Server 2008 Using Table-Valued Parameters by Erland Sommarskog for a comprehensive discussion on this topic.
I have this procedure that executes another procedure passed by a parameter and its parameters datefrom and dateto.
CREATE procedure [dbo].[execute_proc]
#procs varchar(200),
#pdatefrom date,
#pdateto date
as
exec #procs #datefrom=#pdatefrom,#dateto=#pdateto
But I need to also pass the parameters dynamically without the need to edit them in the procedure. For example, what I am imagining is something like this
CREATE procedure [dbo].[execute_proc]
#procs varchar(200),
#params varchar(max)
as
exec #procs #params
where #params is a string like #param1=1,#param2='somethingelse'
Is there a way to do this?
It's not really clear what the point of your wrapper procedure is (auditing? debugging?), and it seems like a very awkward solution. If you explain why you want to do this, someone may have a completely different and hopefully better solution.
The biggest issue with your proposal is that you can only pass parameters as strings and that means you have to handle all the escaping, data conversion/formatting and SQL injection issues that come with dynamic SQL. It would be much better to call each procedure directly, passing correctly typed parameters from your calling code.
Having said all that, if you really want to do it then you can do something like this:
create proc dbo.ExecuteProcedure
#ProcedureName sysname,
#Parameters nvarchar(max),
#Debug bit = 0x0,
#Execute bit = 0x1
as
set nocount on
begin
declare #sql nvarchar(max)
set #sql = 'exec ' + quotename(#ProcedureName) + ' ' + #Parameters
if #Debug = 0x1 print #sql
if #Execute = 0x1 exec(#sql)
end
go
exec dbo.ExecuteProcedure 'dbo.SomeProc', '#p1 = 1, #p2 = ''themhz''s proc''', 0x1, 0x0
You should also have a look at sp_executesql, which does almost exactly what you want, but it needs to have all the parameter data types too, which you say is not possible in your scenario.
Put the stored procedure name in a varchar field in your client table
Retrieve the SP name and assign it to a parameter ( spName) when the client is chosen.
In code create a function that returns a string
function PassStoredProcedureName(spName as string) as string
return spName
end function
Set your dataset to "Stored Procedure"
Open a dataset Expression window
Enter =Code.PassStoredProcedureName(Parameters!spName.value)
When you chose a client, the spName will be assigned to the parameter. When the dataset executes, it will pass the parameter to the function, which will pass the spName to the dataset.
I use this to execute custom stored procedures for clients when the same stored procedure will not work for all clients.
Be sure to normalize the aliased field names so that data retrieval to a report does not break.
Your stored procedures should always have the same parameter requirements even if they are not needed.
In SQL Server 2005, is there a concept of a one-time-use, or local function declared inside of a SQL script or Stored Procedure? I'd like to abstract away some complexity in a script I'm writing, but it would require being able to declare a function.
Just curious.
You can create temp stored procedures like:
create procedure #mytemp as
begin
select getdate() into #mytemptable;
end
in an SQL script, but not functions. You could have the proc store it's result in a temp table though, then use that information later in the script ..
You can call CREATE Function near the beginning of your script and DROP Function near the end.
Common Table Expressions let you define what are essentially views that last only within the scope of your select, insert, update and delete statements. Depending on what you need to do they can be terribly useful.
I know I might get criticized for suggesting dynamic SQL, but sometimes it's a good solution. Just make sure you understand the security implications before you consider this.
DECLARE #add_a_b_func nvarchar(4000) = N'SELECT #c = #a + #b;';
DECLARE #add_a_b_parm nvarchar(500) = N'#a int, #b int, #c int OUTPUT';
DECLARE #result int;
EXEC sp_executesql #add_a_b_func, #add_a_b_parm, 2, 3, #c = #result OUTPUT;
PRINT CONVERT(varchar, #result); -- prints '5'
The below is what I have used i the past to accomplish the need for a Scalar UDF in MS SQL:
IF OBJECT_ID('tempdb..##fn_Divide') IS NOT NULL DROP PROCEDURE ##fn_Divide
GO
CREATE PROCEDURE ##fn_Divide (#Numerator Real, #Denominator Real) AS
BEGIN
SELECT Division =
CASE WHEN #Denominator != 0 AND #Denominator is NOT NULL AND #Numerator != 0 AND #Numerator is NOT NULL THEN
#Numerator / #Denominator
ELSE
0
END
RETURN
END
GO
Exec ##fn_Divide 6,4
This approach which uses a global variable for the PROCEDURE allows you to make use of the function not only in your scripts, but also in your Dynamic SQL needs.
In scripts you have more options and a better shot at rational decomposition. Look into SQLCMD mode (SSMS -> Query Menu -> SQLCMD mode), specifically the :setvar and :r commands.
Within a stored procedure your options are very limited. You can't create define a function directly with the body of a procedure. The best you can do is something like this, with dynamic SQL:
create proc DoStuff
as begin
declare #sql nvarchar(max)
/*
define function here, within a string
note the underscore prefix, a good convention for user-defined temporary objects
*/
set #sql = '
create function dbo._object_name_twopart (#object_id int)
returns nvarchar(517) as
begin
return
quotename(object_schema_name(#object_id))+N''.''+
quotename(object_name(#object_id))
end
'
/*
create the function by executing the string, with a conditional object drop upfront
*/
if object_id('dbo._object_name_twopart') is not null drop function _object_name_twopart
exec (#sql)
/*
use the function in a query
*/
select object_id, dbo._object_name_twopart(object_id)
from sys.objects
where type = 'U'
/*
clean up
*/
drop function _object_name_twopart
end
go
This approximates a global temporary function, if such a thing existed. It's still visible to other users. You could append the ##SPID of your connection to uniqueify the name, but that would then require the rest of the procedure to use dynamic SQL too.
Just another idea for anyone that's looking this up now. You could always create a permanent function in tempdb. That function would not be prefixed with ## or # to indicate it's a temporary object. It would persist "permanently" until it's dropped or the server is restarted and tempdb is rebuilt without it. The key is that it would eventually disappear once the server is restarted if your own garbage collection fails.
The scope of the function would be within TempDB but it could reference another database on the server with 3 part names. (dbname.schema.objectname) or better yet you can pass in all the parameters that the function needs to do its work so it doesn't need to look at other objects in other databases.