The Stored Procedure Interface

This section covers the interface (that is, the input and output parameters) of stored procedures.

Input Parameters

You can define input parameters for a stored procedure in its header. An input parameter must be provided with a value when the stored procedure is invoked unless you assign the parameter with a default value. As an example, the following code creates the usp_GetCustOrders procedure, which accepts a customer ID and datetime range boundaries as inputs, and returns the given customer’s orders in the given datetime range:

USE Northwind;
GO

IF OBJECT_ID('dbo.usp_GetCustOrders') IS NOT NULL
  DROP PROC dbo.usp_GetCustOrders;
GO

CREATE PROC dbo.usp_GetCustOrders
  @custid   AS NCHAR(5),
  @fromdate AS DATETIME = '19000101',
  @todate   AS DATETIME = '99991231'
AS

SET NOCOUNT ON;

SELECT OrderID, CustomerID, EmployeeID, OrderDate
FROM dbo.Orders
WHERE CustomerID = @custid
  AND OrderDate >= @fromdate
  AND OrderDate < @todate;
GO

Tip

Tip

The SET NOCOUNT ON option tells SQL Server not to produce the message saying how many rows were affected for data manipulation language (DML) statements. Some client database interfaces, such as OLEDB, absorb this message as a row set. The result is that when you expect to get a result set of a query back to the client, instead you get this message of how many rows were affected as the first result set. By issuing SET NOCOUNT ON, you avoid this problem in those interfaces, so you might want to adopt the practice of specifying it.

When invoking a stored procedure, you must specify inputs for those parameters that were not given default values in the definition (for @custid in our case). There are two formats for assigning values to parameters when invoking a stored procedure: unnamed and named. In the unnamed format, you just specify values without specifying the parameter names. Also, you must specify the inputs by declaration order of the parameters. You can omit inputs only for parameters that have default values and that were declared at the end of the parameter list. You cannot omit an input between two parameters for which you do specify values. If you want such parameters to use their default values, you would need to specify the DEFAULT keyword for those.

As an example, the following code invokes the procedure without specifying the inputs for the two last parameters, which will use their default values, and produces the output shown in Table 7-3:

EXEC dbo.usp_GetCustOrders N'ALFKI';

Table 7-3. Customer ALFKI’s Orders

OrderID

CustomerID

EmployeeID

OrderDate

10643

ALFKI

6

1997-08-25 00:00:00.000

10692

ALFKI

4

1997-10-03 00:00:00.000

10702

ALFKI

4

1997-10-13 00:00:00.000

10835

ALFKI

1

1998-01-15 00:00:00.000

10952

ALFKI

1

1998-03-16 00:00:00.000

11011

ALFKI

3

1998-04-09 00:00:00.000

If you want to specify your own value for the third parameter but use the default for the second, specify the DEFAULT keyword for the second parameter:

EXEC dbo.usp_GetCustOrders N'ALFKI', DEFAULT, '20060212';

This code also produces the output in Table 7-3.

And, of course, if you want to specify your own values for all parameters, just specify them in order, as in:

EXEC dbo.usp_GetCustOrders N'ALFKI', '19970101', '19980101';

which produces the output shown in Table 7-4:

Table 7-4. Customer ALFKI’s Orders in 1997

OrderID

CustomerID

EmployeeID

OrderDate

10643

ALFKI

6

1997-08-25 00:00:00.000

10692

ALFKI

4

1997-10-03 00:00:00.000

10702

ALFKI

4

1997-10-13 00:00:00.000

These are the basics of stored procedures. You’re probably already familiar with them, but I decided to include this coverage to lead to a recommended practice. There are many maintenance-related issues that can arise when using the unnamed assignment format. You must specify the arguments in order; you must not omit an optional parameter; and by looking at the code, it might not be clear what the inputs actually mean and to which parameter they relate. Therefore, it’s a good practice to use the named assignment format, where you specify the name of the argument and assign it with an input value, as in:

EXEC dbo.usp_GetCustOrders
  @custid   = N'ALFKI',
  @fromdate = '19970101',
  @todate   = '19980101';

The code is much more readable; you can play with the order in which you specify the inputs; and you can omit any parameter that you like if it has a default value.

Output Parameters

Output parameters allow you to return output values from a stored procedure. A change made to the output parameter within the stored procedure is reflected in the variable from the calling batch that was assigned to the output parameter. The concept is similar to a pointer in C or a ByRef parameter in Visual Basic.

As an example, the following code alters the definition of the usp_GetCustOrders procedure, adding to it the output parameter @numrows:

ALTER PROC dbo.usp_GetCustOrders
  @custid   AS NCHAR(5),
  @fromdate AS DATETIME = '19000101',
  @todate   AS DATETIME = '99991231',
  @numrows  AS INT OUTPUT
AS

SET NOCOUNT ON;
DECLARE @err AS INT;

SELECT OrderID, CustomerID, EmployeeID, OrderDate
FROM dbo.Orders
WHERE CustomerID = @custid
  AND OrderDate >= @fromdate
  AND OrderDate < @todate;

SELECT @numrows = @@rowcount, @err = @@error;

RETURN @err;
GO

@numrows will return the number of rows affected by the query. Notice that the stored procedure also uses a RETURN clause to return the value of the @@error function after the invocation of the query.

To get the output parameter back from the stored procedure when invoking it, you will need to assign it with a variable defined in the calling batch and mention the keyword OUTPUT. To get back the return status, you will also need to provide a variable from the calling batch right before the procedure name and an equal sign. Here’s an example:

DECLARE @myerr AS INT, @mynumrows AS INT;

EXEC @myerr = dbo.usp_GetCustOrders
  @custid   = N'ALFKI',
  @fromdate = '19970101',
  @todate   = '19980101',
  @numrows  = @mynumrows OUTPUT;

SELECT @myerr AS err, @mynumrows AS rc;

The stored procedure returns the output shown in Table 7-4, plus it assigns the return status 0 to @myerr and the number of affected rows (in this case, 3) to the @mynumrows variable.

If you want to manipulate the row set returned by the stored procedure with T-SQL, you will need to create a table first and use the INSERT/EXEC syntax, as shown in Example 7-2.

Example 7-2. Send output of usp_GetCustOrders to a table

IF OBJECT_ID('tempdb..#CustOrders') IS NOT NULL
  DROP TABLE #CustOrders;
GO
CREATE TABLE #CustOrders
(
  OrderID    INT      NOT NULL PRIMARY KEY,
  CustomerID NCHAR(5) NOT NULL,
  EmployeeID INT      NOT NULL,
  OrderDate  DATETIME NOT NULL
);

DECLARE @myerr AS INT, @mynumrows AS INT;

INSERT INTO #CustOrders(OrderID, CustomerID, EmployeeID, OrderDate)
  EXEC @myerr = dbo.usp_GetCustOrders
    @custid   = N'ALFKI',
    @fromdate = '19970101',
    @todate   = '19980101',
    @numrows  = @mynumrows OUTPUT;

SELECT OrderID, CustomerID, EmployeeID, OrderDate
FROM #CustOrders;

SELECT @myerr AS err, @mynumrows AS rc;
GO

A client will accept output from a stored procedure into client objects. For example, in ADO programming you define items in a Parameters collection for input parameters, output parameters, and return status. A stored procedure can return more than one result set if within it you invoke multiple queries. In the client code, you will absorb the result sets, moving from one record set to another–for example, using the .NextRecordset property of the Recordset object in ADO. A stored procedure can generate other types of outputs as well, including the output of PRINT and RAISERROR commands. Both would be received by the client through the client interface’s structures–for example, the Errors collection in ADO.

ADO.NET allows you to accept any possible output from a SQL Server stored procedure at the client side. Of course, in order to accept some output, as the first step, you have to execute the procedure. You execute a stored procedure by using the SqlCommand object. To denote you are executing a stored procedure, you have to set the CommandType property of the SqlCommand object to CommandType.StoredProcedure. To define which procedure to execute, you have to insert the name of the procedure to the CommandText property. To actually execute the procedure, use the ExecuteScalar, ExecuteNonQuery, ExecuteReader, or ExecuteXmlReader methods of the SqlCommand object, depending on the output(s) of the stored procedure. Following are the different types of output of stored procedures needed to get the output:

  • A single row set. A single row set can be accepted to an object of the SqlDataReader class for the connected environment (connected means that your application maintains a permanent connection to the SQL Server–that is, the connection is always available), and an object of the SqlDataAdapter class. If you want to fill an object of the DataTable class, which is a member of an object of the DataSet class for the disconnected scenario (disconnected here means that after you read the data in the DataTable, in your application, you can disconnect from SQL Server, and you can still use the data read in your application from the DataTable object).

  • Multiple row sets. The SqlDataReader class. Use the NextResult method of a data reader object to loop through all row sets returned by a stored procedure.

  • Output parameters. Accept output parameters in the Parameters collection of a SqlCommand object. A SqlParameter object in ADO.NET can have four possible directions: Input, Output, InputOutput, or ReturnValue. Of course, a single parameter can have a single direction selected at a time. For ReturnValue direction, please see the next bullet. Input parameters can be used for input only, and output parameters can be used for output only. SQL Server stored procedure output parameters are actually input/output parameters, so you can pass a value through an output parameter when executing a stored procedure. Therefore, you can specify the InputOutput direction of a SqlParameter object in ADO.NET, but you have to assign the input value to it before executing the stored procedure or you will get a compile error.

  • Return valueAccept it in a SqlParameter object with the ReturnValue direction. The return value parameter has to be the first one in the Parameters collection of a SqlCommand object.

  • Number of rows affected. This can be tricky. You can’t rely on the output of SQL Server here, because the developer could add the SET NOCOUNT ON statement to the stored procedure. SqlDataReader objects have the RecordsAffected property, which gets the number of rows updated, inserted, or deleted. For a SELECT statement, this property can’t be used. But there is a problem also with INSERT, UPDATE, and DELETE statements: the RecordAffected property gets only the total number of rows affected by all DML statements in the stored procedure. What if you need the number of rows for each DML statement separately? In this case, you can define as many output parameters as the number of DML statements in the procedure, and then store the @@rowcount value in every output parameter after every DML statement. This way you can easily get the number of rows affected by SELECT statements as well.

  • Errors. All your .NET code should use a Try..Catch block for every risky operation. In the Catch block, you can trap real errors–that is, errors with severity levels greater than 10, meaning significant errors, not just warnings or info messages. You run the statements that can produce an error, like executing a stored procedure by using a SqlCommand object, in the Try block. When an error occurs in the Try block, the control of the application is transferred immediately to the Catch block, where you can access a SqlException object that describes the error. This SqlException object has an Errors collection. In the collection, you get objects of SqlError type, a single object for any error of severity level from 11 through 16 thrown by your SQL Server. You can loop through the collection and read all errors returned by SQL Server. Among the properties of the SqlError are a Number property, which holds the error number, and a Message property, which holds the error message.

  • Warnings. This can be tricky as well. Warnings in SQL Server are error messages with a severity level of 10 or lower. If there is no real error in your code, you can get the warnings in the procedure that handles the InfoMessage event of the SqlConnection object. The InfoMessage event receives a SqlInfoMessageEventArgs object. SqlInfoMessageEventArgs has an Errors collection, which is similar to previously mentioned Errors collection of the SQLException object–it is a collection of objects of SqlError type, this time with SQL Server errors of severity level 10 or lower. Again, you can loop through the collection and get all the information from SQL Server warnings that you need. But if there were a real error in the stored procedure, you could catch all the warnings as well as the errors in the Catch block, and the InfoMessage event would never occur.

  • T-SQL PRINT statement output. You handle this output in the same way that you handle warnings. Read the output using the InfoMessage event handler of a SqlConnection, or read it in a Catch block if there was a real error in the stored procedure.

  • DBCC statement output. Some DBCC commands support the TABLERESULTS option. If you use this option, you can read the output using the SqlDataReader object just as you would read any other row set. If the output of the DBCC statement is textual and not a table, you can get it by using the InfoMessage event of the SqlConnection object. Again, the same rules apply as for warnings and PRINT output.

  • XML output. ADO.NET 2.0 fully supports the new XML data type, so you can simply use a SqlDataReader object to get the results in table format, including XML data type columns. XML output from a SELECT statement with the FOR XML clause can be retrieved into an XmlReader object, and you have to use the ExecuteXmlReader method of the SqlCommand object, of course.

  • User-defined data types (UDTs). ADO.NET fully supports UDTs as well, so you can fetch values of UDT columns the same way you fetch values of columns of native types. Note that SQL Server sends only the values, not the code for the UDT; therefore, to use any of the UDT’s methods at the client side, the code must be available at the client side as well.

  • Schema of a row set retrieved with a SqlDataReaderSqlDataReader in ADO.NET 2.0 has a new method called GetSchemaTable. This method can be used to get a DataTable that describes the column metadata of the SqlDataReader.

For examples and more details about ADO.NET, please refer to ADO.NET Examples and Best Practices for C# Programmers (Apress, 2002) by William R. Vaughn and Peter Blackburn.

When you’re done, run the following code for cleanup:

USE Northwind;
GO
IF OBJECT_ID('dbo.usp_GetCustOrders') IS NOT NULL
  DROP PROC dbo.usp_GetCustOrders;
GO
IF OBJECT_ID('tempdb..#CustOrders') IS NOT NULL
  DROP TABLE #CustOrders;
GO
..................Content has been hidden....................

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