CHAPTER 17

image

Stored Procedures

by Jonathan Gennick

A stored procedure groups one or more Transact-SQL statements into a logical unit, stored as an object in a SQL Server database. After the stored procedure is created, its T-SQL definition is accessible from the sys.sqlmodule catalog view.

When a stored procedure is executed for the first time, SQL Server creates an execution plan and stores it in the plan cache. SQL Server can then reuse the plan on subsequent executions of this stored procedure. Plan reuse allows stored procedures to provide fast and reliable performance compared to noncompiled and unprepared ad hoc query equivalents.

17-1. Selling the Benefits

Problem

You are having trouble selling other developers on the prospect of using stored procedures.

Solution

Recite some of the benefits, which include the following:

  • Stored procedures help centralize your Transact-SQL code in the data tier. Web sites or applications that embed ad hoc SQL are notoriously difficult to modify in a production environment. When ad hoc SQL is embedded in an application, you may spend too much time trying to find and debug the embedded SQL. Once you’ve found the bug, chances are you’ll need to recompile the program executable, causing unnecessary application outages or application distribution nightmares. If you centralize your Transact-SQL code in stored procedures, you’ll have a centralized place to look for SQL code or SQL batches. If you document and standardize the code properly, your stored procedures will improve overall supportability of the application.
  • Stored procedures can reduce network traffic for larger ad hoc queries. Programming your application to execute a stored procedure, rather than push across a 500-line SQL call, can have a positive impact on your network and application performance, particularly if the call is repeated thousands of times a minute.
  • Stored procedures encourage code reusability. For example, if your web application uses a drop-down menu containing a list of cities and this drop-down is used in multiple web pages, you can call the stored procedure from each web page rather than embed the same SQL in multiple places.
  • Stored procedures allow you to obscure the method of data retrieval. If you change the underlying tables from which the source data is pulled, stored procedures (similar to views) can obscure this change from the application. This allows you to make changes without forcing a code change at the application tier. You can swap in new tables for the old, and as long as the same columns and data types are sent back to the application, the application is none the wiser.
  • Stored procedures can do more than views. They can take advantage of control-of-flow techniques, temporary tables, table variables, and much more.
  • Stored procedures have a stabilizing influence on query response time. If you’ve worked extensively with ad hoc queries, you may have noticed that sometimes the amount of time it takes to return results from a query can vary wildly. This may be because of external factors, such as concurrent activity against the table (locking) or resource issues (memory, CPU). On the other hand, an ad hoc query may be performing erratically because SQL Server periodically chooses less efficient execution plans. With stored procedures, you gain more reliable query-plan caching and hence reuse. Notice that I use the word reliable here, rather than faster. Ad hoc queries can sometimes perform better than their stored procedure counterparts, but it all depends on the circumstances in which the execution plan was cached and how you have tested, tuned, and then implemented the code within.

How It Works

There are many good reasons to use stored procedures, and very few bad ones. Usually, reasons against using stored procedures come from application developers who are more comfortable writing ad hoc SQL within the application tier and may not be trained in the use of stored procedures. In companies with a separate application and database administration staff, stored procedures also imply a loss of control over the Transact-SQL code from the application developer to the database administration staff. Assuming your database administration team is competent and willing to assist with a move to stored procedures in a timely fashion, the benefits of using them should far outweigh any loss of control.

If none of the reasons in the solution convinces your colleagues that stored procedures are largely beneficial, then also review the security benefits. Direct table access (or worse, sysadmin access) to the SQL Server instance and its database poses a security risk. For example, if someone gets hold of the inline code, he’ll be able to glean information about the underlying schema of your database and direct his hacking attempts accordingly. Keeping all SQL within stored procedures keeps only the stored procedure reference in the application instead of each individual column and table name.

Another security benefit to stored procedures is that you can grant database users and/or database roles access to them specifically instead of having to grant direct access to tables. The stored procedures can act as a control layer, allowing you to choose which columns and rows can and cannot be modified by the stored procedure (and hence also by the caller).

17-2. Creating a Stored Procedure

Problem

You want to create a simple stored procedure. For example, you want to return the results from a SELECT statement.

Solution

Issue a CREATE PROCEDURE statement. The first parameters are the schema and new procedure name. Next is the Transact-SQL body of your stored procedure. The body contains SQL statements implementing one or more tasks that you want to accomplish. The following example demonstrates creating a stored procedure that queries the database and returns a list of customers having known names (that is, who have corresponding entries in Person.Person):

CREATE PROCEDURE dbo.ListCustomerNames
AS
       SELECT  CustomerID,
               LastName,
               FirstName
       FROM    Sales.Customer sc
               INNER JOIN Person.Person pp
                   ON sc.CustomerID = pp.BusinessEntityID
       ORDER BY LastName,
                FirstName;

Next, the new stored procedure is executed using the EXEC command, which is shorthand for EXECUTE.

EXEC dbo.ListCustomerNames;

This returns the following results:

CustomerID LastName FirstName
----------- -------------- --------------
         285 Abbas Syed
         293 Abel Catherine
         295 Abercrombie Kim
         38 Abercrombie Kim
         211 Abolrous Hazem
         ...

How It Works

This recipe demonstrates creating a stored procedure that queries the contents of two tables, returning a result set. This stored procedure works like a view, but it will have a cached query plan when executed for the first time, which will also make its execution time consistent in consecutive executions.

The example started off by creating a stored procedure called ListCustomerNames.

CREATE PROCEDURE dbo.ListCustomerNames
AS

The procedure is created in the dbo schema. The letters dbo stand for “database owner.” The dbo schema is one that is always present in a database, and it can be a convenient repository for stored procedures.

image Tip  Regardless of target schema, it is good practice to specify that schema explicitly when creating a stored procedure. By doing so, you ensure there is no mistake as to where the procedure is created.

The Transact-SQL query definition then follows the AS keyword:

       SELECT   CustomerID,
                LastName,
                FirstName
       FROM     Sales.Customer sc
                INNER JOIN Person.Person pp
                    ON sc.CustomerID = pp.BusinessEntityID
       ORDER BY LastName,
                FirstName;

After the procedure is created, it is executed using the EXEC command:

EXEC dbo.ListCustomerNames;

During the stored procedure creation process, SQL Server checks that the SQL syntax is correct, but it doesn’t check for the existence of referenced tables. This means you can reference a table name incorrectly, and the incorrect name will not cause an error until runtime. That process of checking names at runtime is called deferred name resolution . It is actually helpful in that it allows you to create or reference objects that don’t exist yet. It also means that you can drop, alter, or modify the objects referenced in the stored procedure without invalidating the procedure.

image Tip  Avoid problems from deferred name resolution by testing queries ad hoc whenever conveniently possible. That way, you can be sure your syntax is correct and that names resolve properly before creating the procedure.

17-3. Generalizing a Stored Procedure

Problem

You want to pass values to a stored procedure to affect its behavior or the results it returns. For example, you want to pass an account number and get the customer’s name in return. You also want to specify whether the name is returned in all-uppercase letters.

Solution

Parameterize the stored procedure. Define one or more parameters between the procedure name and the AS keyword when creating the procedure. Enclose your parameter list within parentheses. Preface each parameter name with an @ character.

For example, the following procedure returns the customer name associated with the account number passed as the first parameter. Use the second parameter to control whether the name is forced to uppercase.

CREATE PROCEDURE dbo.LookupByAccount
(@AccountNumber VARCHAR(10),
 @UpperFlag CHAR(1))
AS
       SELECT     CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(FirstName)
                    ELSE FirstName
                  END AS FirstName,
                  CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(LastName)
                    ELSE LastName
                  END AS LastName
       FROM       Person.Person
       WHERE      BusinessEntityID IN (SELECT CustomerID
                                  FROM Sales.Customer
                                  WHERE AccountNumber = @AccountNumber) ;

Invoke this procedure as follows:

EXEC LookupByAccount 'AW00000019', 'u';

The results from this invocation should be as follows:

FirstName     LastName
---------     --------
MARY          DEMPSEY

You can pass the second parameter in either uppercase or lowercase. Pass any letter but 'U' or 'u' to leave the name in mixed case while it is stored in the database. Here’s an example:

EXEC LookupByAccount 'AW00000019', 'U';
EXEC LookupByAccount 'AW00000019', 'x';
FirstName     LastName
---------     ----------
MARY          DEMPSEY
...
FirstName     LastName
---------     ----------
Mary          Dempsey

How It Works

The earlier Recipe 17-2 demonstrates a nonparameterized stored procedure, meaning that no external parameters are passed to it. The ability to pass parameters is part of why stored procedures are one of the most important database object types in SQL Server. Using parameters, you can pass information into the body of a procedure in order to control how the procedure operates and to pass values that cause queries to return needed results.

The solution example shows a procedure having two input parameters. The first parameter is an account number. The second is a flag controlling whether the results are forced to uppercase. The procedure queries the database to find out the name of the person behind the number.

Developers executing the procedure given in the recipe solution do not need to worry about how the underlying query is written; they can simply accept that they provide an account number and get back a name. You are able to change the implementation when needed without affecting the interface and thus without having to change any of the code invoking the procedure.

You’re able to make parameters optional by giving default values. Recipe 17-4 shows how. You’re also able to return values through so-called output parameters, and Recipe 17-6 shows how to do that.

17-4. Making Parameters Optional

Problem

You want to make certain parameters optional. For example, you are tired of having to always pass an 'x' to the LookupByAccount procedure. You want your names back in mixed case but without ruining it for those who pass a 'U' to force uppercase.

Solution

Re-create the procedure and make the @UpperFlag parameter optional. First, drop the version of the procedure currently in place from Recipe 17-3.

DROP PROCEDURE dbo.LookupByAccount;

Then, create a new version of the procedure having a default value specified for @UpperFlag. Do that by appending = 'x' following the parameter’s data type. Here’s an example:

CREATE PROCEDURE dbo.LookupByAccount
(@AccountNumber VARCHAR(10),
 @UpperFlag CHAR(1) = 'x')
AS
       SELECT     CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(FirstName)
                    ELSE FirstName
                  END AS FirstName,
                  CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(LastName)
                    ELSE LastName
                  END AS LastName
       FROM       Person.Person
       WHERE      BusinessEntityID IN (SELECT CustomerID
                                  FROM Sales.Customer
                                  WHERE AccountNumber = @AccountNumber) ;

Now, you can invoke the procedure without needing to specify that pesky flag.

EXEC LookupByAccount 'AW00000019';
FirstName     LastName
---------     -----------
Mary          Dempsey

But others who want their results forced to uppercase are still free to do that.

EXEC LookupByAccount 'AW00000019', 'U';
FirstName     LastName
---------     -----------
MARY          DEMPSEY

The default value takes effect whenever the parameter is not specified but can be overridden when needed.

How It Works

The solution example makes a parameter optional by specifying a default value as follows:

@UpperFlag VARCHAR(1) = 'x'

It’s now possible to invoke the procedure by passing only an account number. The default value takes effect in that case, and the person’s name is returned unchanged, without being forced to uppercase.

It’s common to specify default parameters at the end of the parameter list. Doing so makes it easier to invoke a procedure in an ad hoc manner from SQL Management Studio. Also, many are used to this convention. However, you can define optional parameters earlier in the list, and the next recipe shows how.

17-5. Making Early Parameters Optional

Problem

The parameter you want to make optional precedes one that is mandatory. You are thus unable to skip that parameter even though you’ve specified a default value for it.

Solution

Invoke your procedure using named notation rather than positional notation. In doing so, you can name the parameter that you do want to pass, and it won’t matter where in the list that parameter occurs.

For example, begin with the following version of the procedure from Recipe 17-4. In this version, the UpperFlag parameter comes first.

CREATE PROCEDURE dbo.LookupByAccount2
(@UpperFlag CHAR(1) = 'x',
@AccountNumber VARCHAR(10))
AS

       SELECT     CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(FirstName)
                    ELSE FirstName
                  END AS FirstName,
                  CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(LastName)
                    ELSE LastName
                  END AS LastName
       FROM       Person.Person
       WHERE      BusinessEntityID IN (SELECT CustomerID
                                       FROM   Sales.Customer
                                       WHERE  AccountNumber = @AccountNumber) ;

Using named notation, you can pass just the account number as follows:

EXEC LookupByAccount2 @AccountNumber = 'AW00000019';

You can use the DEFAULT keyword to make it explicit that you are accepting a default parameter value for @UpperFlag.

EXEC LookupByAccount2 @AccountNumber = 'AW00000019', @UpperFlag = DEFAULT;

Using named notation, you can specify the parameter values in any order.

How It Works

It’s common to pass parameters using positional notation. Named notation takes some extra typing but in return can be a bit more self-documenting. That’s because each procedure invocation names all the parameters, helping you remember later what the associated parameter values represent.

Using named notation also allows you to specify parameters in any order. That ability allows you to skip parameters having default values, no matter where those parameters occur in the list. Don’t try to mix the two notations. SQL Server requires that you choose one or the other. Specify all parameters by name or all by position. Don’t try to mix the two approaches.

17-6. Returning Output

Problem

You are writing a stored procedure. You want to return values to the code calling the procedure.

Solution

Specify some parameters as OUTPUT parameters. The following example creates a stored procedure that returns the list of departments for a specific group. In addition to returning the list of departments, an OUTPUT parameter is defined to return the number of departments found for the specific group.

CREATE PROCEDURE dbo.EL_Department
       @GroupName NVARCHAR(50),
       @DeptCount INT OUTPUT
AS
       SELECT Name
       FROM   HumanResources.Department
       WHERE  GroupName = @GroupName
       ORDER BY Name;
       SELECT @DeptCount = @@ROWCOUNT;

Now you can define a local variable to hold the output and invoke the procedure. Here’s an example:

DECLARE @DeptCount INT;
EXEC dbo.SEL_Department 'Executive General and Administration',
  @DeptCount OUTPUT;
PRINT @DeptCount;

The query in the procedure generates the row set. The PRINT command displays the row count value passed back through the @DeptCount variable. The query in this example returns the following five rows:

Name
--------------------------
Executive
Facilities and Maintenance
Finance
Human Resources
Information Services

Next, the stored procedure uses the PRINT statement to return the count of rows. If you’re executing the query ad hoc using Management Studio, you will see the value 5 on the Messages tab if you are viewing results as a grid and at the bottom of the Results tab if you are viewing results as text. Here’s an example:

5

How It Works

The solution begins by creating a stored procedure with a defined parameter called @DeptCount, followed by the data type and OUTPUT keyword:

@DeptCount INT OUTPUT

The stored procedure executes the query and then stores the row count in the parameter.

SELECT @DeptCount = @@ROWCOUNT

The invoking code creates the following variable to pass as the output parameter:

DECLARE @DeptCount INT

The EXEC statement must also specify that a parameter is an output parameter. Do that by following the passed value with the OUTPUT keyword, as in the following:

EXEC dbo.SEL_Department 'Executive General and Administration',
  @DeptCount OUTPUT;

You can use OUTPUT parameters as an alternative or additional method for returning information back to the caller of the stored procedure. If you’re using OUTPUT only to communicate information back to the calling application, it’s usually just as easy to create a second result set containing the information you need. This is because .NET applications, for example, can easily consume the multiple result sets that are returned from a stored procedure. The technique of using OUTPUT parameters versus using an additional result set to return information is often just a matter of preference.

17-7. Modifying a Stored Procedure

Problem

You have an existing stored procedure and want to change its behavior.

Solution

Redefine the procedure using the ALTER PROCEDURE command. You can change everything but the original stored procedure name. The syntax is almost identical to CREATE PROCEDURE.

The following example modifies the stored procedure created in the previous recipe in order to return the number of departments returned by the query as a separate result set instead of via an OUTPUT parameter.

ALTER PROCEDURE dbo.SEL_Department
  @GroupName NVARCHAR(50)
AS
  SELECT   Name
  FROM     HumanResources.Department
  WHERE    GroupName = @GroupName
  ORDER BY Name;
  SELECT   @@ROWCOUNT AS DepartmentCount;

You may now execute the stored procedure as follows, and two result sets are returned:

EXEC dbo.SEL_Department 'Research and Development';
Name
------------------------
Engineering
Research and Development
Tool Design
DepartmentCount
---------------
        3

How It Works

ALTER PROCEDURE is used to modify the definition of an existing stored procedure, in this case both removing a parameter and adding a second result set. You can change everything but the procedure name. Using ALTER PROCEDURE also preserves any existing permissions on the stored procedure. If you drop and re-create the procedure, you’ll need to re-grant permissions. Using ALTER PROCEDURE avoids the need for that tedium.

17-8. Removing a Stored Procedure

Problem

You are no longer using a stored procedure and want to remove it from your database.

Solution

Drop the stored procedure from the database using the DROP PROCEDURE command. Here’s an example:

DROP PROCEDURE dbo.SEL_Department;

How It Works

Once a stored procedure is dropped, its definition is removed from the database’s system tables. Any cached query execution plans are also removed for that stored procedure. Code references to the stored procedure by other procedures or triggers will fail upon execution once the stored procedure has been dropped.

17-9. Automatically Run a Stored Procedure at Start-Up

Problem

You want to execute some code every time a particular instance is started. For example, you might want to document start-up times or clear out work tables on each restart.

Solution

Invoke the sp_procoption system stored procedure to designate a procedure that you write to execute automatically upon instance start-up. In the example to follow, a stored procedure is set to execute automatically whenever SQL Server is started. First, set the database context to the master database (which is the only place that start-up stored procedures can be placed).

USE master;

Next, create a start-up logging table. Do this because the procedure this recipe creates as an example writes to this table. Here is the creation statement:

CREATE TABLE dbo.SQLStartupLog
       (
        SQLStartupLogID INT IDENTITY(1, 1)
                            NOT NULL
                            PRIMARY KEY,
        StartupDateTime DATETIME NOT NULL
       );

Then create a stored procedure to insert a value into the new table.

CREATE PROCEDURE dbo.INS_TrackSQLStartups
AS
       INSERT dbo.SQLStartupLog
        (StartupDateTime)
       VALUES (GETDATE());

Finally, invoke sp_procoption to set this new procedure to execute when the SQL Server service restarts.

EXEC sp_procoption @ProcName = 'INS_TrackSQLStartups',
  @OptionName = 'startup', @OptionValue = 'true';

From now on, starting the instance triggers execution of the stored procedure, which in turn inserts a row into the table to log the start-up event.

How It Works

This recipe creates a new table in the master database to track SQL Server start-ups. A stored procedure is also created in the master database to insert a row into the table with the current date and time of execution.

image Caution  We are not espousing the creation of objects in the system databases, because it isn’t generally a good idea to create them there. However, if you must use auto-execution functionality as discussed in this recipe, you have no choice but to create your objects in the system database.

The stored procedure must be created in the master database; otherwise, you’ll see the following error message when trying to use sp_procoption:

Msg 15398, Level 11, State 1, Procedure sp_procoption, Line 73 Only objects in the master database owned by dbo can have the startup setting changed.

To disable the stored procedure, execute the following command:

EXEC sp_procoption @ProcName = 'INS_TrackSQLStartups',
  @OptionName = 'startup', @OptionValue = 'false'

Setting @OptionValue to false disables the start-up procedure.

image Note  If you’re going to test further recipes in this chapter, be sure to execute USE AdventureWorks2012 to change your database back to the example database generally being used in this chapter.

17-10. Viewing a Stored Procedure’s Definition

Problem

You want to view the definition for a stored procedure so that you can ascertain exactly how that procedure operates.

Solution

From an ad hoc session, it’s often easiest to execute sp_helptext. Here’s an example:

EXEC sp_helptext 'LookupByAccount';

Your results will be in the form of a CREATE PROCEDURE statement:

Text
------------------------------------------------------------------------------
CREATE PROCEDURE dbo.LookupByAccount
(@AccountNumber VARCHAR(10),
 @UpperFlag VARCHAR(1) = 'x')
AS
       SELECT     CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(FirstName)
                    ELSE FirstName
                  END AS FirstName,
                  CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(LastName)
                    ELSE LastName
                  END AS LastName
       FROM       Person.Person
       WHERE      BusinessEntityID IN (SELECT CustomerID
                                       FROM   Sales.Customer
                                       WHERE  AccountNumber = @AccountNumber) ;

From code, you may prefer to query sys.sql_modules and related catalog views. Doing so allows access to a great wealth of information from code, information that you can use in writing helpful utilities to manage objects in your database. For example, execute the following query to retrieve the definition for the stored procedure created in Recipe 17-3:

SELECT definition
FROM sys.sql_modules m
        INNER JOIN sys.objects o
        ON m.object_id = o.object_id
WHERE o.type = 'P'
        AND o.name = 'LookupByAccount';

Your results will be the following output showing the definition in the form of a CREATE PROCEDURE statement. (If outputting as text, be sure to set the maximum number of characters displayed in each column to something higher than the default of just 256.)

definition
-------------------------------------------------------------------------------
CREATE PROCEDURE dbo.LookupByAccount
(@AccountNumber VARCHAR(10),
 @UpperFlag CHAR(1) = 'x')
AS
       SELECT     CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(FirstName)
                    ELSE FirstName
                  END AS FirstName,
                  CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(LastName)
                    ELSE LastName
                  END AS LastName
       FROM       Person.Person
       WHERE      BusinessEntityID IN (SELECT CustomerID
                                       FROM   Sales.Customer
                                       WHERE  AccountNumber = @AccountNumber) ;

You can save these results and execute them to re-create the procedure at some future time or on another database server.

How It Works

Invoke sp_helptext whenever you want to see the definition for a stored procedure or other user-defined object. You’ll get the result back in the form of a single text value.

Query the view sys.sql_modules to retrieve the definitions of stored procedures, triggers, views, and other SQL-defined objects. Join sys.sql_modules to sys.objects to gain access to object names and types. For example, the solution query specifically requested o.type = 'P'. That is the type code used to indicate stored procedures.

The two system views expose several other columns giving useful information or that you can use to restrict query results to only procedures and other objects of interest. It’s worth reviewing the view definitions (by visiting the SQL Server Books Online manual set) to become familiar with the values available.

17-11. Documenting Stored Procedures

Problem

You are writing a stored procedure and want to leave some notes for the next person (perhaps it will be yourself!) who must maintain that procedure.

Solution

Define a format for stored procedure headers that includes room for commentary and for a history of change over time. The following is an example of a standard stored procedure header:

CREATE PROCEDURE dbo.IMP_DWP_FactOrder AS
-- Purpose: Populates the data warehouse, Called by Job
-- Maintenance Log
-- Update By Update Date
Description
-- Joe Sack 8/15/2008 Created
-- Joe Sack 8/16/2008 A new column was added to
--the base table, so it was added here as well.
... Transact-SQL code here

For brevity, the stored procedure examples in this chapter have not included extensive comments or headers. However, in your production database, you should at the very least define headers for each stored procedure created in a production database.

How It Works

This recipe is more of a best practice rather than a review of a command or function. It is important to comment your stored procedure code very well so that future support staff, authors, and editors will understand the business rules and intents behind your Transact-SQL code. Although some code may seem self-evident at the time of authoring, the original logic may not seem so clear a few months after it was written. Business logic is often transient and difficult to understand over time, so including a written description of that logic in the body of the code can save hours of troubleshooting and investigation.

image Caution  One drawback of making your code self-documenting is that other developers who edit your code may not include documentation of their own changes. You may end up being blamed for code you didn’t write, just because you were the last person to log a change. This is where your company should strongly consider a source control system to track all check-in and check-out activities, as well as be able to compare changes between procedure versions.

No doubt you’ll see other procedure headers out in the field with much more information. Don’t demand too much documentation. Include enough to bring clarity, but not so much that you introduce redundancy. For example, if you include the stored procedure name in the header, in addition to the actual CREATE PROCEDURE statement, you’ll soon start seeing code in which the header name doesn’t match the stored procedure name. Why not just document the information that isn’t already included in the stored procedure definition? That is the approach we recommend.

17-12. Determining the Current Nesting Level

Problem

You are developing a stored procedure that invokes itself or a set of procedures that invoke each other. You want to detect programaticallyhow deeply nested you are in the call stack.

Solution

Execute a query to retrieve the @@NESTLEVEL value. This value begins at zero and is incremented by one for each procedure call. The following are two CREATE PROCEDURE statements to set up the solution example:

-- First procedure
CREATE PROCEDURE dbo.QuickAndDirty
AS
SELECT @@NESTLEVEL;
GO
-- Second procedure
CREATE PROCEDURE dbo.Call_QuickAndDirty
AS
SELECT @@NESTLEVEL
EXEC dbo.QuickAndDirty;
GO

After creating these two stored procedures, execute the following set of statements to demonstrate the operation of @@NESTLEVEL:

SELECT @@NESTLEVEL;
EXEC dbo.Call_QuickAndDirty;

Your results should be as follows:

-----------
        0
...
-----------
        1
...
-----------
        2

How It Works

@@NESTLEVEL returns the current nesting level for the stored procedure context. A stored procedure nesting level indicates how many times a stored procedure has called another stored procedure. SQL Server allows stored procedures to make up to a maximum of 32 nested (incomplete) calls.

The solution example begins by creating two stored procedures. One of those procedures invokes the other. The final query and procedure execution show that @@NESTLEVEL begins at zero. It is incremented and reported as 1 by the Call_QuickAndDirty procedure when that procedure is invoked by the EXEC statement. Then @@NESTLEVEL is incremented one more time when the first-invoked stored procedure executes QuickAndDirty.

17-13. Encrypting a Stored Procedure

Problem

You want to encrypt a stored procedure to prevent others from querying the system catalog views to view your code.

Solution

Create the procedure using the WITH ENCRYPTION option. Specify the option after the name of the new stored procedure, as the next example demonstrates:

CREATE PROCEDURE dbo.SEL_EmployeePayHistory
       WITH ENCRYPTION
AS
       SELECT     BusinessEntityID,
                  RateChangeDate,
                  Rate,
                  PayFrequency,
                  ModifiedDate
       FROM       HumanResources.EmployeePayHistory;

Once you’ve created WITH ENCRYPTION, you’ll be unable to view the procedure’s text definition. You can try to query for the definition:

EXEC sp_helptext SEL_EmployeePayHistory;

but you will receive only the following message:

The text for object 'SEL_EmployeePayHistory' is encrypted.

Even querying the system catalog directly won’t be of help. For example, you can try this:

SELECT definition
FROM   sys.sql_modules m
       INNER JOIN sys.objects o
           ON m.object_id = o.object_id
WHERE   o.type = 'P'
        AND o.name = 'SEL_EmployeePayHistory';

and you will be rewarded with only an empty result:

definition
----------
NULL

The procedure’s definition is encrypted, and there is nothing you can do to retrieve the definition. So, be sure to keep a copy outside the database.

How It Works

Stored procedure definitions can have their contents encrypted in the database, removing the ability to read a procedure’s definition later. Software vendors who use SQL Server in their back end often encrypt stored procedures in order to prevent tampering or reverse engineering from clients or competitors. If you use encryption, be sure to save the original T-SQL definition, because it can’t easily be decoded later (legally and reliably, anyhow). Also perform your encryption only prior to a push to production.

image Caution  Be sure to save your source code, because the encrypted text cannot be decrypted easily.

17-14. Specifying a Security Context

Problem

You want to specify the source for the rights and privileges under which a stored procedure executes. For example, you might want a caller to be able to execute a procedure but not also to have the privileges needed to execute the SELECT statements that the procedure executes internally.

Solution

Create or alter the procedure and specify the EXECUTE AS clause to define the security context under which a stored procedure is executed, regardless of the caller. The options for EXECUTE AS in a stored procedure are as follows:

EXECUTE AS { CALLER | SELF | OWNER | 'user_name' }

The default behavior for EXECUTE AS is the CALLER option, which means that the permissions of the executing user are used (and if the user doesn’t have proper access, that execution will fail). If the SELF option is used, the execution context of the stored procedure will be that of the user who created or last altered the stored procedure. When the OWNER option is designated, the owner of the stored procedure’s schema is used. The user_name option is an explicit reference to a database user under whose security context the stored procedure will be executed.

The following example creates a version of SEL_Department that is owned by HumanResources. The clause EXECUTE AS OWNER specifies that invocations of the procedure will run under the rights and privileges granted to the schema owner.

CREATE PROCEDURE HumanResources.SEL_Department
      @GroupName NVARCHAR(50)
WITH EXECUTE AS OWNER
AS
  SELECT     Name
  FROM       HumanResources.Department
  WHERE      GroupName = @GroupName
  ORDER BY   Name;
  SELECT     @@ROWCOUNT AS DepartmentCount;

How It Works

SQL Server implements a concept termed ownership chaining that comes into play when a stored procedure is created and used to perform an INSERT, UPDATE, DELETE, or SELECT against another database object. If the schema of the stored procedure object is the same as the schema of the object referenced within, SQL Server checks only that the stored procedure caller has EXECUTE permissions to the stored procedure.

Ownership chaining applies only to the INSERT, UPDATE, DELETE, or SELECT commands. This is why stored procedures are excellent for securing the database, because you can grant a user access to execute a stored procedure without giving the user access to the underlying tables.

An issue arises, however, when you are looking to execute commands that are not INSERT, UPDATE, DELETE, or SELECT. In those situations, even if a caller has EXECUTE permissions to a stored procedure that, for example, truncates a table using the TRUNCATE TABLE command, she must still have permissions to use the TRUNCATE TABLE command in the first place. You may not want to grant such broad permission.

Using EXECUTE AS, you can create the procedure to run as the schema owner or as a user that you specify. You need not grant permission for TRUNCATE TABLE to all users who might invoke the procedure but only to the user you specify in the security context.

The same “gotcha” goes for dynamic SQL within a stored procedure. SQL Server will ensure that the caller has both EXECUTE permission and the appropriate permissions in order to perform the task the dynamic SQL is attempting to perform, even if that dynamic SQL is performing an INSERT, UPDATE, DELETE, or SELECT. Specifying a security context lets you avoid granting those privileges broadly to all users who might need to invoke the procedure.

17-15. Avoiding Cached Query Plans

Problem

Your procedure produces wildly different query results based on the application calling it because of varying selectivity of qualified columns, so much so that the retained execution plan causes performance issues when varying input parameters are used.

Solution

Force a recompilation upon each invocation of the procedure. Do that by including the WITH RECOMPILE clause when creating (or altering) the procedure. Here’s an example:

ALTER PROCEDURE dbo.LookupByAccount2
  (
       @UpperFlag VARCHAR(1) = 'x',
       @AccountNumber VARCHAR(10)
  )
  WITH RECOMPILE
AS
       SELECT     CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(FirstName)
                    ELSE FirstName
                  END AS FirstName,
                  CASE UPPER(@UpperFlag)
                    WHEN 'U' THEN UPPER(LastName)
                    ELSE LastName
                  END AS LastName
       FROM       Person.Person
       WHERE      BusinessEntityID IN (SELECT CustomerID
                                       FROM   Sales.Customer
                                       WHERE  AccountNumber = @AccountNumber) ;

Now whenever this procedure is called, a new execution plan will be created by SQL Server.

How It Works

Recompilations occur automatically when underlying table or other object changes occur to objects that are referenced within a stored procedure. They can also occur with changes to indexes used by the plan or after a large number of updates to table keys referenced by the stored procedure. The goal of an automatic recompilation is to make sure the SQL Server execution plan is using the most current information and not using out-of-date assumptions about the schema and data.

SQL Server is able to perform statement-level recompiles within a stored procedure, instead of recompiling the entire stored procedure. Because recompiles cause extra overhead in generating new plans, statement-level recompiles help decrease this overhead by correcting only what needs to be corrected.

After every recompile, SQL Server caches the execution plan for use until the next time a change to an underlying object triggers another recompile. Cached query plans are a good thing but sometimes can cause inefficient plans to be chosen. Parameter sniffing, for example, is the process of deferring the generation of an execution plan until the first invocation of a query or procedure, at which time parameter values are examined and a plan is chosen based upon those values passed that very first time. The problem sometimes arises that a plan good for one set of values is actually terrible with another set. The problem can sometimes be bad enough that it is best to recompile at each execution. That is what the solution example accomplishes.

The solution example specifies WITH RECOMPILE to ensure that a query plan is not cached for the procedure during creation or execution. It is rare to need the option, because generally the cached plan chosen by SQL Server will suffice. Use the option if you want to take advantage of a stored procedure’s other benefits such as security and modularization but don’t want SQL Server to store an inefficient plan (such as from a “parameter sniff”) based on wildly varying result sets.

17-16. Flushing the Procedure Cache

Problem

You want to remove all cached query plans from the plan cache. For example, you might want to test procedure performance against a so-called cold cache, reproducing the cache as though SQL Server had just been restarted.

image Caution  Think very carefully before unleashing this recipe in a production environment because you could be knocking out several cached query plans that are perfectly fine.

Solution

Execute the DBCC FREEPROCCACHE command to clear existing cached plans. If you like, you can query the number of cached query plans first. Here’s an example:

SELECT COUNT(*) 'CachedPlansBefore'
FROM sys.dm_exec_cached_plans;
CachedPlansBefore
-----------------
        20

This example shows 20 cached plans. Your results may vary, depending upon the number of procedures you have executed. Clear the cached plans by executing DBCC FREEPROCCACHE as follows, and retrieve the number of cached plans again. Here’s an example:

DBCC FREEPROCCACHE;
SELECT COUNT(*) 'CachedPlansAfter'
FROM sys.dm_exec_cached_plans;

You should see output similar to the following:

DBCC execution completed. If DBCC printed error messages, contact your system administrator.
CachedPlansAfter
----------------
        0

How It Works

DBCC FREEPROCCACHE clears the procedure cache. The count of cached plans both before and after will vary based on the activity on your SQL Server instance. The query against sys.dm_exec_cached_plans shows one way to retrieve the count of plans currently in the cache. Background processes and jobs that may be running before and after the clearing of the cache can affect the results, and you may not necessarily see a zero for the number of cached plans after you’ve cleared the cache.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.208.25