SQL Server allows the use of the EXEC
statement in stored procedures to execute dynamic SQL statements. This
capability allows you to do things such as pass in object names as
parameters and dynamically execute a query against the table name passed
in, as in the following example:
IF EXISTS ( SELECT * FROM sys.procedures WHERE schema_id = schema_id('dbo') AND name = N'get_order_data') DROP PROCEDURE dbo.get_order_data GO create proc get_order_data (@table varchar(30), @column varchar(30), @value int) as declare @query varchar(255)
select @query = 'select * from ' + @table + ' where ' + @column + ' = ' + convert(varchar(10), @value)
EXEC (@query)
return
This feature can be useful
when you have to pass a variable list of values into a stored procedure.
The string contains a comma-separated list of numeric values or
character strings, just as they would appear inside the parentheses of
an IN clause. If you are passing character strings, you need to be sure to put single quotation marks around the values, as shown in Listing 1.
Listing 1. Passing a Variable List of Values into a Stored Procedure
IF EXISTS ( SELECT * FROM sys.procedures WHERE schema_id = schema_id('dbo') AND name = N'find_books_by_type') DROP PROCEDURE dbo.find_books_by_type GO create proc find_books_by_type @typelist varchar(8000) as
exec ('select title_id, title = substring(title, 1, 40), type, price from titles where type in (' + @typelist + ') order by type, title_id') go
set quoted_identifier off exec find_books_by_type "'business', 'mod_cook', 'trad_cook'" go
title_id title type price -------- ---------------------------------------- ------------ ------------------ BU1032 The Busy Executive's Database Guide business 14.9532 BU1111 Cooking with Computers: Surreptitious Ba business 14.595 BU2075 You Can Combat Computer Stress! business 15.894 BU7832 Straight Talk About Computers business 14.9532 MC2222 Silicon Valley Gastronomic Treats mod_cook 14.9532 MC3021 The Gourmet Microwave mod_cook 15.894 TC3218 Onions, Leeks, and Garlic: Cooking Secre trad_cook 0.0017 TC4203 Fifty Years in Buckingham Palace Kitchen trad_cook 14.595 TC7777 Sushi, Anyone? trad_cook 14.3279
|
When using dynamic SQL in stored procedures, you need to be aware of a few issues:
Any local variables that are declared and assigned values in the constructed string within an EXEC statement are not available to the stored procedure outside the EXEC command. The lifespan of a local variable is limited to the context in which it is declared, and the context of the EXEC command ends when it completes.
Any
local variables that are declared and assigned values in the stored
procedure can be used to build the dynamic query statement, but the
local variables cannot be referenced by any statements within the EXEC string. The commands in the EXEC
statement run in a different context from the stored procedure, and you
cannot reference local variables declared outside the current context. Commands executed in an EXEC
string execute within the security context of the user executing the
procedure, not the execution context of the stored procedure. By
default, if a user has permission to execute a stored procedure, that
user also has implied permission to access all objects referenced in the
stored procedure that are owned by the same person who created the
stored procedure. However, if a user has permission to execute the
procedure but hasn’t explicitly been granted the permissions necessary
to perform all the actions specified in the EXEC string, a permission violation occurs at runtime. If you issue a USE command to change the database context in an EXEC statement, it is in effect only during the EXEC string execution. It does not change the database context for the stored procedure (see Listing 2).
Listing 2. Changing Database Context in an EXEC Statement
use bigpubs2008 go create proc db_context as print db_name() exec ('USE AdventureWorks print db_name()') print db_name() go
exec db_context go
bigpubs2008 AdventureWorks bigpubs2008
|
Using sp_executesql
If you want to have the flexibility of dynamic SQL but better
persistence of parameterized execution plans, you should consider using sp_executesql instead of EXEC in your stored procedures. The syntax for sp_executesql is as follows:
sp_executesql @SQL_commands, @parameter_definitions, param1,...paramN
sp_executesql operates just as the EXEC statement with regard to the scope of names, permissions, and database context. However, sp_executesql
is more efficient for executing the same SQL commands repeatedly when
the only change is the values of the parameters. Because the SQL
statement remains constant and only the parameters change, SQL Server is
more likely to reuse the execution plan generated for the first
execution and simply substitute the new parameter values. This saves the
overhead of having to compile a new execution plan each time.
Listing 3 provides an example of a stored procedure that takes up to three parameters and uses sp_executesql to invoke the dynamic queries.
Listing 3. Invoking Dynamic Queries in a Procedure by Using sp_executesql
IF EXISTS ( SELECT * FROM sys.procedures WHERE schema_id = schema_id('dbo') AND name = N'find_books_by_type2') DROP PROCEDURE dbo.find_books_by_type2 GOgo create proc find_books_by_type2 @type1 char(12), @type2 char(12) = null, @type3 char(12) = null as
exec sp_executesql N'select title_id, title = substring(title, 1, 40), type, price from bigpubs2008.dbo.titles where type = @type', N'@type char(12)', @type = @type1 if @type2 is not null exec sp_executesql N'select title_id, title = substring(title, 1, 40), type, price from bigpubs2008.dbo.titles where type = @type', N'@type char(12)', @type = @type2 if @type3 is not null exec sp_executesql N'select title_id, title = substring(title, 1, 40), type, price from bigpubs2008.dbo.titles where type = @type', N'@type char(12)', @type = @type3 go
set quoted_identifier off exec find_books_by_type2 'business', 'mod_cook', 'trad_cook' go
title_id title type price -------- ---------------------------------------- ------------ ------------------ BU1032 The Busy Executive's Database Guide business 14.9532 BU1111 Cooking with Computers: Surreptitious Ba business 14.595 BU2075 You Can Combat Computer Stress! business 15.894 BU7832 Straight Talk About Computers business 14.9532
title_id title type price -------- ---------------------------------------- ------------ ------------------ MC2222 Silicon Valley Gastronomic Treats mod_cook 14.9532 MC3021 The Gourmet Microwave mod_cook 15.894
title_id title type price -------- ---------------------------------------- ------------ ------------------ TC3218 Onions, Leeks, and Garlic: Cooking Secre trad_cook 0.0017 TC4203 Fifty Years in Buckingham Palace Kitchen trad_cook 14.595 TC7777 Sushi, Anyone? trad_cook 14.3279
|
Note that the SQL command and parameter definition parameters to sp_executesql must be of type nchar, nvarchar, or ntext. Also, to ensure that the query plan is reused, make sure that the object names are fully qualified in the SQL command.
Using Output Parameters with sp_executesql
One important concept to
remember about dynamic SQL is that it runs in a separate scope from the
stored procedure that invokes it. This is similar to when a stored
procedure executes another stored procedure. Because local variables are
available only within the current scope, a nested procedure cannot
access a local variable declared in the calling procedure. Similarly,
you cannot access a local variable declared outside the scope of a
dynamic SQL statement. With stored procedures, you can work around this
limitation by using input and output parameters to pass values into and
out of a nested stored procedure.
If you use sp_executesql
to execute dynamic SQL, you can use output parameters to pass values
both into and out of the dynamic SQL query through local variables. As
described in the previous section, the second parameter to sp_executesql
is a comma-separated list that defines the parameters you will be using
within the dynamic SQL statement. As with parameter definitions for a
stored procedure, parameters passed to sp_executesql
can also be defined as output parameters. To get the values back out,
you define the parameter as an output parameter in the parameter list
and then specify the OUTPUT keyword when passing the variable in the corresponding argument list for sp_executesql.
Listing 4 shows an example of a stored procedure that uses sp_executesql
to execute a dynamic SQL query and return a value via an output
parameter. You can use the parameters inside the dynamic SQL–like
parameters inside a stored procedure. Any values assigned to output
parameters within the dynamic SQL query are passed back to the local
variable in the calling procedure.
Listing 4. Using Output Parameters in sp_executesql
IF EXISTS ( SELECT * FROM sys.procedures WHERE schema_id = schema_id('dbo') AND name = N'get_avg_price') DROP PROCEDURE dbo.get_avg_price GO create proc get_avg_price @dbname sysname, @type varchar(12) = '%' as
declare @dsql nvarchar(500), @avgval float
/********************************************************* ** build the dynamic query using the @avg and @type as ** variables, which will be passed in via sp_executesql **********************************************************/ select @dsql = 'select @avg = avg(isnull(price, 0)) from ' + @dbname+ '..titles ' + 'where type like @type'
/************************************************************* ** submit the dynamic query using sp_executesql, passing type ** as an input parameter, and @avgval as an output parameter ** The value of @avg in the dynamic query will be passed ** back into @avgval *************************************************************/ exec sp_executesql @dsql, N'@avg float OUT, @type varchar(12)', @avgval OUT, @type print 'The avg value of price for the titles table' + ' where type is like ''' + @type + ''' in the ' + @dbname + ' database' + ' is ' + ltrim(str(@avgval, 9,4))
go
exec get_avg_price @dbname = 'bigpubs2008', @type = 'business' go
The avg value of price for the titles table where type is like 'business' in the bigpubs2008 database is 15.0988
exec get_avg_price @dbname = 'bigpubs2008', @type = DEFAULT go
The avg value of price for the titles table where type is like '%' in the bigpubs2008 database is 0.3744
|