For the purpose of the following code, assume that a linked server connection has been established to the local server with the nameNOLI: SELECT * FROM OpenQuery MAUINOLI, ‘EXEC OBXKites
Trang 1Because the result set of the stored procedure is returned via a function being used by a data source in
theFROMclause of theSELECTstatement, aWHEREclause can further reduce the output of the stored
procedure
While this technique enables the use of stored procedures within aSELECTstatement, it’s not as
opti-mized as the technique of passing any row restrictions to the stored procedure for processing within the
stored procedure The only benefit of usingopenquery()is that it enables a complex stored procedure
to be called from within an ad hoc query
For the purpose of the following code, assume that a linked server connection has been established to
the local server with the nameNOLI:
SELECT * FROM OpenQuery(
MAUINOLI,
‘EXEC OBXKites.dbo.pProductCategory_Fetch;’) WHERE ProductCategoryDescription Like ‘%stuff%’;
Result:
ProductCategoryName ProductCategoryDescription -
Best Practice
If you need to call complex code within a SELECT statement, using openquery() to call a stored
procedure works, but the syntax is a bit bizarre A better method is to use a CASE expression or create a
user-defined function
Executing remote stored procedures
Two methods exist for calling a stored procedure located on another server: a four-part name reference
and a distributed query Both methods require that the remote server be a linked server Stored
proce-dures may only be called remotely; they may not be created remotely.
The remote stored procedure may be executed by means of the four-part name:
server.database.schma.procedurename
For example, the following code adds a new product category to theOBXKitesdatabase on Noli’s (my
development server) second instance of SQL Server:
EXEC [MAUINoli\SQL2COPENHAGEN].OBXKites.dbo.pProductCategory_AddNew
‘Food’, ‘Eatables’;
Trang 2Alternately, theOpenQuery()function can be used to call a remote stored procedure:
OpenQuery(linked server name, ‘exec ‘EXEC stored procedure;’)
The next code sample executes thepCustomerType_Fetchstored procedure in the default database
for the user login being used to connect toMAUI\COPENHAGENNoli\SQL2 If the default database is
incorrect, a three-part name can be used to point to the correct database
SELECT CustomerTypeName, DiscountPercent, [Default]
FROM OPENQUERY(
[MAUI\COPENHAGENNoli\SQL2], ‘EXEC OBXKites.dbo.pCustomerType_Fetch;’);
Result:
CustomerTypeName DiscountPercent Default
- -
As with any other distributed query, the Distributed Transaction Coordinator service must be running if
the transaction updates data in more than one server
Passing Data to Stored Procedures
A stored procedure is more useful when it can be manipulated by parameters TheCategoryList
stored procedure created previously returns all the product categories, but a procedure that performs a
task on an individual row requires a method for passing the row ID to the procedure
SQL Server stored procedures may have numerous input and output parameters (SQL Server 2005
increased the number of parameters from 1,024 to 2,100 to be exact)
Input parameters
You can add input parameters that pass data to the stored procedure by listing the parameters after the
procedure name in theCREATE PROCEDUREcommand Each parameter must begin with an@sign,
and becomes a local variable within the procedure Like local variables, the parameters must be defined
with valid data types When the stored procedure is called, the parameter must be included (unless the
parameter has a default value)
The following code sample creates a stored procedure that returns a single product category The
@CategoryNameparameter can accept Unicode character input up to 35 characters in length
The value passed by means of the parameter is available within the stored procedure as the variable
@CategoryNamein theWHEREclause:
USE OBXKites;
Trang 3go CREATE PROCEDURE dbo.CategoryGet (
@CategoryName NVARCHAR(35) )
AS SELECT ProductCategoryName, ProductCategoryDescription FROM dbo.ProductCategory
WHERE ProductCategoryName = @CategoryName;
go When the following code sample is executed, the Unicode string literal ‘Kite’is passed to the stored
procedure and substituted for the variable in theWHEREclause:
EXEC dbo.CategoryGet N’Kite’;
Result:
ProductCategoryName ProductCategoryDescription -
stunt, to Chinese, to novelty kites
If multiple parameters are involved, the parameter name can be specified in any order, or the
parame-ter values listed in order If the two methods are mixed, then as soon as the parameparame-ter is provided by
name, all the following parameters must be as well
The next four examples each demonstrate calling a stored procedure and passing the parameters by
original position and by name:
EXEC Schema.StoredProcedure
@Parameter1 = n,
@Parameter2 = ‘n’;
EXEC Schema.StoredProcedure
@Parameter2 = ‘n’,
@Parameter1 = n;
EXEC Schema.StoredProcedure n, ‘n’;
EXEC Schema.StoredProcedure n, @Parameter2 = ‘n’;
Parameter defaults
You must supply every parameter when calling a stored procedure, unless that parameter has been
cre-ated with a default value You establish the default by appending an equal sign and the default to the
parameter, as follows:
CREATE PROCEDURE Schema.StoredProcedure (
@Variable DataType = DefaultValue )
Trang 4The following code, extracted from theOBXKitessample database, demonstrates a stored procedure
default If a product category name is passed in this stored procedure, the stored procedure returns only
the selected product category However, if nothing is passed, theNULLdefault is used in theWHERE
clause to return all the product categories:
CREATE PROCEDURE dbo.pProductCategory_Fetch2
@Search NVARCHAR(50) = NULL
If @Search = null then return all ProductCategories
If @Search is value then try to find by Name
AS
SET NOCOUNT ON;
SELECT ProductCategoryName, ProductCategoryDescription
FROM dbo.ProductCategory
WHERE ProductCategoryName = @Search
OR @Search IS NULL;
IF @@RowCount = 0
BEGIN;
RAISERROR(
‘Product Category ‘’%s" Not Found.’,14,1,@Search);
END;
The first execution passes a product category:
EXEC dbo.pProductCategory_Fetch2 ‘OBX’;
Result:
ProductCategoryName ProductCategoryDescription
-
WhenpProductCategory_Fetchexecutes without a parameter, the@Searchparameter’s default of
NULLallows theWHEREclause to evaluate to true for every row, as follows:
EXEC dbo.pProductCategory_Fetch2;
Result:
ProductCategoryName ProductCategoryDescription
-
stunt, to Chinese, to novelty kites
and Outer Banks videos
Trang 5Table-valued parameters
New to SQL Server 2008 are table-valued parameters (TVPs) The basic idea is that a table can be created
and populated in the client application or T-SQL and then passed, as a table variable, into a stored
pro-cedure or user-defined function This is no small thing Chapter 1, ‘‘The World of SQL Server,’’ listed
TVPs as my favorite new feature for developers and the number two top new feature overall
In every complex application I’ve worked on, there’s been a requirement that a complex transaction be
passed to the database — orders and order details, flights and flight legs, or an object and a dynamic set
of attributes In each case, the complete transaction includes multiple types of items
Without considering TVPs, there are three traditional ways to solve the problem of how to pass multiple
types of items in a transaction to SQL Server — none of them particularly elegant:
■ Pass each item in a separate stored procedure call —AddNewOrder,AddNewOrder Details— repeatedly for every item, and thenCloseNewOrder This method has two primary problems First, it’s very chatty, with multiple trips to the server Second, if the client does not complete the transaction, a process on the server must clean up the remains of the unfinished transaction
■ Package the entire transaction at the client into XML, pass the XML to SQL Server, and shred the XML into the relational tables This solution is significantly cleaner than the first method, but there’s still the extra work of shredding the data in the server
■ Concatenate any data that includes multiple rows into a comma-delimited string and pass that
as avarchar(max)parameter into the stored procedure Like the XML solution, this method does achieve a single stored procedure call for the complex transaction, but it involves even more coding complexity than the XML method and will prove to be difficult to maintain
Table-valued parameters provide a fourth alternative A table can be created in ADO.NET 3.5 or in
T-SQL and passed into a SQL Server stored procedure or user-defined function
The following script creates a test order, order detail scenario:
USE tempdb;
Create Table dbo.Orders(
OrderID INT NOT NULL IDENTITY Constraint OrdersPK Primary Key, OrderDate DateTime,
CustomerID INT );
Create Table dbo.OrderDetails(
OrderID INT NOT NULL Constraint OrderDetailsFKOrders References Orders, LineNumber SmallInt NOT NULL,
ProductID INT );
the XML solution (not printed here) and the TVP solution (listed here).
Trang 6The first step is to define a table type in SQL Server for consistency when creating the TVP:
CREATE TYPE OrderDetailsType AS Table (
LineNumber INT,
ProductID INT,
IsNew BIT,
IsDirty BIT,
IsDeleted BIT
);
Once the table type is created, it can be seen in Object Explorer in the Database➪ Programmability ➪
Types➪ User-Defined Table Types node
With the table type established, the stored procedure can now be created that references the table type
The table must be defined as the table type name with theREADONLYoption The TVP parameter will
look like a table variable inside the stored procedure
Code can reference the data as a normal other data source except that it’s read-only Some have
criticized this as a major drawback I don’t see it that way If I want to modify the data and return it, I
think the best way is to return the data as a selected result set
Here’s the stored procedure It simply returns the data from the TVP so it’s easy to see the TVP
in action:
CREATE
alter
PROC OrderTransactionUpdateTVP (
@OrderID INT OUTPUT,
@CustomerID INT,
@OrderDate DateTime,
@Details as OrderDetailsType READONLY
)
AS
SET NoCount ON ;
Begin Try
Begin Transaction;
If @OrderID is NULL then it’s a new order, so Insert Order
If @OrderID IS NULL
BEGIN;
Insert Orders(OrderDate, CustomerID)
Values (@OrderDate, @CustomerID);
Get OrderID value from insert SET @OrderID = Scope_Identity();
END;
Test view of the data
SELECT * FROM @Details ;
Commit Transaction;
End Try
Trang 7Begin Catch;
RollBack;
End Catch RETURN;
To test table-valued parameters, the following script creates a table variable as the previously defined
table type, populates it using the new row constructers, and then calls the stored procedure:
Declare @OrderID INT;
DECLARE @DetailsTVP as OrderDetailsType;
INSERT @DetailsTVP (LineNumber,ProductID,IsNew,IsDirty,IsDeleted) VALUES
(5, 101, -1, -1, 0), (2, 999, 0, -1, 0), (3, null, 0, 0, 0);
exec OrderTransactionUpdateTVP
@OrderID = @OrderID Output ,
@CustomerID = ‘78’,
@OrderDate = ‘2008/07/24’,
@Details = @DetailsTVP;
Result:
LineNumber ProductID IsNew IsDirty IsDeleted - -
I’ve already converted some of my software to TVP and have found that not only does the TVP syntax
make sense and simplify a nasty problem with an elegant solution, but TVPs are fast Under the
cov-ers, TVPs leverage SQL Server’s bulk insert technology, which is by far the fastest way to move data into
SQL Server
Returning Data from Stored Procedures
SQL Server provides five means of returning data from a stored procedure A batch can return data via
aSELECTstatement or aRAISERRORcommand Stored procedures inherit these from batches and add
output variables and theRETURNcommand And, the calling stored procedure can create a table that
the called stored procedure populates
This section walks through the methods added by stored procedures and clarifies their scope and
purpose
Trang 8Output parameters
Output parameters enable a stored procedure to return data to the calling client procedure The
key-wordOUTPUTis required both when the procedure is created and when it is called Within the stored
procedure, the output parameter appears as a local variable In the calling procedure or batch, a
vari-able must have been created to receive the output parameter When the stored procedure concludes, its
current value is passed to the calling procedure’s local variable
Although output parameters are typically used solely for output, they are actually two-way parameters
Best Practice
Output parameters are useful for returning single units of data when a whole record set is not required
For returning a single row of information, using output parameters is blazingly faster than preparing a
record set
The next code sample uses an output parameter to return the product name for a given product code
from theProducttable in theOBXKitessample database To set up for the output parameter:
1 The batch declares the local variable@ProdNameto receive the output parameter
2 The batch calls the stored procedure, using@ProdNamein theEXECcall to the stored
procedure
3 Within the stored procedure, the@ProductNameoutput parameter/local variable is created in
the header of the stored procedure The initial value isNULLuntil it is initialized by code
4 Inside the stored procedure, theSELECTstatement sets@ProductNameto ‘Basic Box Kite
21 inch’, the product name for the product code ‘1001’
5 The stored procedure finishes and execution is passed back to the calling batch The value is
transferred to the batch’s local variable,@ProdName
6 The calling batch uses thePRINTcommand to send@ProdNameto the user
This is the stored procedure:
USE OBXKites;
go
CREATE PROC dbo.GetProductName (
@ProductCode CHAR(10),
@ProductName VARCHAR(25) OUTPUT
)
AS
SELECT @ProductName = ProductName
FROM dbo.Product
WHERE Code = @ProductCode;
RETURN;
Trang 9This is the calling batch:
USE OBXKites;
DECLARE @ProdName VARCHAR(25);
EXEC dbo.GetProductName ‘1001’, @ProdName OUTPUT;
PRINT @ProdName;
Result:
Basic Box Kite 21 inch
Unit Testing
Icombine agile development with unit testing when designing and developing a database, using three
scripts:
■ Create: The first script includes all the DDL code and creates the database, tables, and
all stored procedures and functions
■ Sample: The second script includes the sample data in the form of INSERT VALUES
statements and is used to load the data for unit testing
■ ProcTest: The last script executes every procedure, checking the output against what is
expected from the sample data
Sequentially executing the three scripts unit tests every procedure
Using the Return Command
ARETURNcommand unconditionally terminates the procedure and returns an integer value to the
call-ing batch or client Technically, a return can be used with any batch, but it can only return a value from
a stored procedure or a function
When calling a stored procedure, theEXECcommand must use a local integer variable if the returned
status value is to be captured:
EXEC @LocalVariable = StoredProcedureName;
I’ve seen stored procedures in production at different companies that use the return code for everything
from success/failure, to the number of rows processed, to the@@errorcode Personally, I prefer to use
the return code for success/failure, and pass back any other data in parameters, orRAISERROR The
most important consideration is that the database is 100% consistent in the use ofRETURN
The following basic stored procedure returns a success or failure status, depending on the parameter:
Trang 10CREATE PROC dbo.IsItOK (
@OK VARCHAR(10)
)
AS
IF @OK = ‘OK’
BEGIN;
RETURN 0;
END;
ELSE
BEGIN;
RETURN -100;
END;
The calling batch:
DECLARE @ReturnCode INT;
EXEC @ReturnCode = dbo.IsItOK ‘OK’;
PRINT @ReturnCode;
EXEC @ReturnCode = dbo.IsItOK ‘NotOK’;
PRINT @ReturnCode;
Result:
0
-100
Path and scope of returning data
Any stored procedure has five possible methods of returning data (SELECT,RAISERROR, external
table,OUTPUTparameters, andRETURN) Deciding which method is right for a given stored procedure
depends on the quantity and purpose of the data to be returned, and the scope of the method used to
return the data The return scope for the five methods is as follows:
■ Selected record sets are passed to the calling stored procedure If the calling stored procedure
consumes the result set (e.g.,INSERT .EXEC) then the result set ends there If the calling
stored procedure does not consume the result set, then it is passed up to the next calling
stored procedure or client
■ RETURNvalues, andOUTPUTparameters are all passed to local variables in the immediate
calling procedure or batch within SQL Server
■ RAISERRORis passed to the calling stored procedure and will continue to bubble up until it
is trapped by aTRY .CATCHor it reaches the client application
If SQL Server Management Studio (the client application) executes a batch that calls stored procedure A,
which then calls stored procedure B, there are multiple ways procedure B can pass data back to
proce-dure A or to the client application, as illustrated in Figure 24-2