63

I know this has been asked to death, and I know why SQL Server doesn't let you do it.

But is there any workaround for this, other than using Extended Stored Procedures?

And please don't tell me to convert my function into a procedure...

So what I'm really asking is: Is there ANY way to run a Stored Procedure from within a Function?

EDIT:

Point proven: there is a way around it, but it's so WRONG I wouldn't do it. I'm gonna change it to a Stored Procedure and execute it elsewhere.

Smur
  • 3,075
  • 7
  • 28
  • 46
  • 1
    +1 for the question as the general advice against doing it might serve as a warning to somebody else who was thinking of doing this. – Tom Chantler Jun 14 '11 at 14:41
  • @Smur: There is another way that I describe in my answer below that, while still potentially problematic when executed in a multi-row context, is far less "wrong" than using `xp_cmdshell` to call **osql** (which really should be **SQLCMD** anyway ;-). – Solomon Rutzky Aug 12 '15 at 14:14

5 Answers5

33

EDIT: I haven't tried this, so I can't vouch for it! And you already know you shouldn't be doing this, so please don't do it. BUT...

Try looking here: http://sqlblog.com/blogs/denis_gobo/archive/2008/05/08/6703.aspx

The key bit is this bit which I have attempted to tweak for your purposes:

DECLARE @SQL varchar(500)

SELECT @SQL = 'osql -S' +@@servername +' -E -q "exec dbName..sprocName "'

EXEC master..xp_cmdshell @SQL
Trisped
  • 5,705
  • 2
  • 45
  • 58
Tom Chantler
  • 14,753
  • 4
  • 48
  • 53
  • Yeah I know I shouldn't... and I'm guessing I wont. Still, I forgot about cmdshell. I had read about using it somewhere... Anyway, thanks a lot. – Smur Jun 14 '11 at 14:19
  • 15
    Part of me drooled when I read this. While a much bigger part of me screamed in terror and ran around the room chirping like a chicken... Seriously, don't find hacks that allow a function to have side-effects if you want to have a usable, friendly environment for any longer than, say, tomorrow... – MatBailie Jun 14 '11 at 14:20
  • @Dems: I agree with you entirely. Doing this sort of thing (other than as an academic exercise) should probably get you fired! – Tom Chantler Jun 14 '11 at 14:22
  • 3
    If the OP does this you want to get Me fired!? That's so harsh ;( – MatBailie Jun 14 '11 at 14:23
  • @Dems I really laughed at how much you emphasized that... And calm down, I know I shouldn't be doing it, and I won't. But I bet you know how it is to mantain applications. – Smur Jun 14 '11 at 14:24
  • Calm down guys, I won't do it. And now I'm even considering deleting the question, haha. – Smur Jun 14 '11 at 14:25
  • 7
    Me? Calm!? I am CALM! Look, I'm hardly shaking at all... What do you mean chirping like a Chicken? It wasn't me I tell you! It wasn't me! STOP STARING AT ME LIKE THAT!!! – MatBailie Jun 14 '11 at 14:26
  • @Dems: don't worry, people take responsibility for their own actions. Unless you thought you should be fired for running round the room squawking like a chicken! – Tom Chantler Jun 14 '11 at 14:27
  • 1
    Lol. Should I keep it or delete it? – Smur Jun 14 '11 at 14:29
  • If it can be expressed as a question, and responded to with an answer, I'd say it's valid to keep it on here... – MatBailie Jun 14 '11 at 14:30
  • 12
    Plus people would laugh at these comments. Keeping it. – Smur Jun 14 '11 at 14:31
  • This might work for executing stored procedures, but if you try to capture the results generated by the stored procedure in an insert statement, you will get this error: "Invalid use of a side-effecting operator 'INSERT EXEC' within a function." – Jimbo Oct 19 '18 at 16:12
22

Functions are not allowed to have side-effects such as altering table contents.

Stored Procedures are.

If a function called a stored procedure, the function would become able to have side-effects.


So, sorry, but no, you can't call a stored procedure from a function.

MatBailie
  • 83,401
  • 18
  • 103
  • 137
  • 4
    Your answer lacks explanation. The main point is *why* functions are not allowed, you're just rephrasing the same sentence into three different forms. If it's about referential transparency, then *state* that. If it's about efficiency, *state* that, but don't leave people wondering. – Luka Ramishvili May 10 '12 at 05:59
  • 7
    @LukaRamishvili - I would contradict you slightly there. `Smoking causes cancer because smoking is carcenogenic` is indeed circular. But `Functions can't call StoredProcedures because Functions are not allowed to cause side-effects` is not circular. There may an *extra* question of `Why can't FN's cause side-effects?` but it really is an extra question. `Why?` can be asked to just about any level, in the end detailing the design and implementation of the optimiser. This isn't such a forum, in my opinion. – MatBailie May 10 '12 at 08:58
  • 1
    Let's not argue about this, you're right. OP didn't ask why, and your answer was straight - "no". But it sounds like a doctrine that [T-SQL] functions aren't allowed side effects. It's important to clarify what's restricting it, the reason *why in the first place aren't functions allowed to have side effects*, and it would explain the reason storprocs aren't allowed either. – Luka Ramishvili May 10 '12 at 11:32
  • 1
    @LukaRamishvili Probably the reason is that the exact number of executions and timing of the executions is execution plan dependant. The same query can have different number of function executions dependant on whether they were evaluated before or after a filter for example. It can even vary with the same plan for a cold cache vs warm cache. [example](http://sqlblog.com/blogs/paul_white/archive/2013/08/31/sql-server-internals-nested-loops-prefetching.aspx) – Martin Smith Dec 07 '14 at 21:10
  • 2
    This answer is categorically incorrect given the 3 methods of doing this shown in the other answers. Whether or not one _should_ do this is separate, and that depends on context (i.e. is the function running once as in `SET @Variable = function` or possibly many times due to being part of a query that sees many possible rows, even if it only returns a single row). Also, it's not true that "If a function called a stored procedure, the function would become able to have side-effects" since SQL Server can prevent just those forbidden actions, as it does when calling a proc from a SQLCLR function. – Solomon Rutzky Aug 12 '15 at 15:11
2

Another option, in addition to using OPENQUERY and xp_cmdshell, is to use SQLCLR (SQL Server's "CLR Integration" feature). Not only is the SQLCLR option more secure than those other two methods, but there is also the potential benefit of being able to call the stored procedure in the current session such that it would have access to any session-based objects or settings, such as:

  • temporary tables
  • temporary stored procedures
  • CONTEXT_INFO

This can be achieved by using "context connection = true;" as the ConnectionString. Just keep in mind that all other restrictions placed on T-SQL User-Defined Functions will be enforced (i.e. cannot have any side-effects).

If you use a regular connection (i.e. not using the context connection), then it will operate as an independent call, just like it does when using the OPENQUERY and xp_cmdshell methods.

HOWEVER, please keep in mind that if you will be using a function that calls a stored procedure (regardless of which of the 3 noted methods you use) in a statement that affects more than 1 row, then the behavior cannot be expected to run once per row. As @MartinSmith mentioned in a comment on @MatBailie's answer, the Query Optimizer does not guarantee either the timing or number of executions of functions. But if you are using it in a SET @Variable = function(); statement or SELECT * FROM function(); query, then it should be ok.

An example of using a .NET / C# SQLCLR user-defined function to execute a stored procedure is shown in the following article (which I wrote):

Stairway to SQLCLR Level 2: Sample Stored Procedure and Function

Solomon Rutzky
  • 46,688
  • 9
  • 128
  • 171
0

Here is another possible workaround:

if exists (select * from master..sysservers where srvname = 'loopback')
    exec sp_dropserver 'loopback'
go
exec sp_addlinkedserver @server = N'loopback', @srvproduct = N'', @provider = N'SQLOLEDB', @datasrc = @@servername
go

create function testit()
    returns int
as
begin
    declare @res int;
    select @res=count(*) from openquery(loopback, 'exec sp_who');
    return @res
end
go

select dbo.testit()

It's not so scary as xp_cmdshell but also has too many implications for practical use.

Vadzim
  • 24,954
  • 11
  • 143
  • 151
  • How would you add a parameter to this. – Siyabonga Dube Apr 04 '21 at 15:07
  • 1
    @SiyabongaDube, the link in my answer covers that with an example. Excerpt: "The second parameter to OPENQUERY is the query to run on the remote server, and you may expect to be able to use a variable here, but you cannot. The query string must be a constant, since SQL Server needs to be able to determine the shape of the result set at compile time. This means that you as soon your query has a parameter value, you need to use dynamic SQL.". – Vadzim Apr 05 '21 at 12:25
0

I have figured out a solution to this problem. We can build a Function or View with "rendered" sql in a stored procedure that can then be executed as normal.

1.Create another sproc

CREATE PROCEDURE [dbo].[usp_FunctionBuilder]
DECLARE @outerSql VARCHAR(MAX)
DECLARE @innerSql VARCHAR(MAX)

2.Build the dynamic sql that you want to execute in your function (Example: you could use a loop and union, you could read in another sproc, use if statements and parameters for conditional sql, etc.)

SET @innerSql = 'your sql'

3.Wrap the @innerSql in a create function statement and define any external parameters that you have used in the @innerSql so they can be passed into the generated function.

SET @outerSql = 'CREATE FUNCTION [dbo].[fn_GeneratedFunction] ( @Param varchar(10))
RETURNS TABLE
AS
RETURN
' + @innerSql;


EXEC(@outerSql)

This is just pseudocode but the solution solves many problems such as linked server limitations, parameters, dynamic sql in function, dynamic server/database/table name, loops, etc.

You will need to tweak it to your needs, (Example: changing the return in the function)

C Rudolph
  • 522
  • 7
  • 6