Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
(ebook) Visual Studio .NET Mastering Visual Basic.pdf
Скачиваний:
120
Добавлен:
17.08.2013
Размер:
15.38 Mб
Скачать

STORED PROCEDURES 917

This time the Query Builder will generate the following SQL statement:

SELECT

dbo.titles.title AS Title,

 

dbo.authors.au_lname + ‘, ‘ + dbo.authors.au_fname AS Author

FROM

dbo.authors LEFT OUTER JOIN dbo.titleauthor

ON dbo.authors.au_id = dbo.titleauthor.au_id

FULL OUTER JOIN dbo.titles

ON dbo.titleauthor.title_id = dbo.titles.title_id

When executed, this statement will return authors without books as well as titles without author:

<NULL>

Greene, Morningstar

The Psychology of Computer Cooking

<NULL>

Notice that the outer join is between the Titles and TitleAuthor tables, as well as between the TitleAuthor and Authors tables. Once each title is linked to the proper row(s) in the TitleAuthor table, the corresponding names will be easily retrieved from the Authors table with an inner join.

Each row in the TitleAuthor table points to a single row of Authors table. You can use the Diagram pane of the SQL Query Builder to experiment with the various types of joins. Right-click the line that connects two tables (it represents a join) and change the type of the join—check or clear the two options on the context menu.

Stored Procedures

This is another of the objects you must familiarize yourself with. Stored procedures are short programs that are executed on the server and perform very specific tasks. Any action you perform against the database frequently should be coded as a stored procedure, so that you can call it from within any application or from different parts of the same application. A stored procedure that retrieves customers by name is a typical example, and you’ll call this stored procedure from many different placed in your application.

You should use stored procedures for all the operations you want to perform against the database. Stored procedures isolate programmers from the database and minimize the risk of impairing the database’s integrity. When all programmers access the same stored procedure to add a new invoice to the database, they don’t have to know the structure of the tables involved or in what order to update these tables. They simply call the stored procedure passing the invoice’s fields as arguments. Another benefit of using stored procedures to update the database is that you don’t risk implementing the same operation in two different ways. This is especially true for a team of developers, because some developers may have not understood the business rules thoroughly. If the business rules change, you can modify the stored procedures accordingly, without touching the other parts of the application.

Another advantage of using stored procedures is that they’re compiled by SQL Server and they’re executed faster. There’s no penalty in using stored procedures versus SQL statements, and any SQL statement can be easily turned into a stored procedure, as you will see in this section. Stored procedures contain traditional programming statements that allow you to validate arguments, use default argument values, and so on. The language you use to write stored procedure is called T-SQL, and it’s a superset of SQL.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

918 Chapter 20 DATABASES: ARCHITECTURE AND BASIC CONCEPTS

ADO.NET makes heavy use of stored procedures by design. You can also use SQL statements to query or update the database, but once you’ve gotten the SQL statement right, you can very easily turn it into a stored procedure so that all other programmers in your team can use it. Stored procedures are stored in the database, and you can prevent developers from modifying them (the database administrator will give each team the proper rights to create, edit, or delete database objects).

So, what’s the difference between stored procedures and SQL statements? As you recall, SQL is a peculiar language: it allows you to specify what you want to do, but not how to do it. Unlike VB, it’s a nonprocedural language. It lacks the control flow statements you expect to find in any programming languages, it doesn’t use variables, and you can’t break a complicated query into smaller procedures. SQL Server extends SQL by adding traditional programming structures. The new language is called T-SQL (Transact-SQL). I won’t discuss T-SQL in depth in this book, but you’ll get a good idea of the capabilities of T-SQL through the examples of the following sections; plus, the basic components of the T-SQL language are overviewed in the bonus chapter “Transact-SQL” on the companion CD. Knowing VB, you’ll have no problem learning the basics of T-SQL.

Let’s explore stored procedures by looking at an existing one. Open the Server Explorer toolbox, connect to the Northwind database and them expand the Stored Procedures node of the Northwind database. Locate the SalesByCategory stored procedure and double-click its name. The SalesByCategory stored procedure contains the statements from Listing 20.2, which will appear on the editor’s window:

Listing 20.2: The SalesByCategory Stored Procedure

ALTER PROCEDURE dbo.SalesByCategory @CategoryName nvarchar(15), @OrdYear nvarchar(4) = ‘1998’

AS

IF @OrdYear != ‘1996’ AND @OrdYear != ‘1997’ AND @OrdYear != ‘1998’ BEGIN

SELECT @OrdYear = ‘1998’ END

SELECT ProductName,

TotalPurchase = ROUND(SUM(CONVERT(decimal(14,2),

OD.Quantity * (1-OD.Discount) * OD.UnitPrice)), 0) FROM [Order Details] OD, Orders O, Products P, Categories C WHERE OD.OrderID = O.OrderID

AND OD.ProductID = P.ProductID

AND P.CategoryID = C.CategoryID AND C.CategoryName = @CategoryName

AND SUBSTRING(CONVERT(nvarchar(22), O.OrderDate, 111), 1, 4) = @OrdYear GROUP BY ProductName

ORDER BY ProductName

This type of code is probably new to you. You’ll learn it quite well as you go along, because it’s really required in coding database applications. You can rely on the various wizards to create stored procedures for you, but you should be able to understand how they work.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

STORED PROCEDURES 919

The first statement alters the procedure SalesByCategory, which is already stored in the database. If it’s a new procedure, you can use the CREATE statement instead of ALTER, to attach a new stored procedure to the database. The following lines until the AS keyword are the parameters of the stored procedure. All variables in T-SQL start with the @ symbol. @CategoryName is a 15-character string, and @OrdYear is a string that also has a default value. If you omit the second argument when calling the SalesByCategory procedure, then the year 1998 will be used automatically.

The AS keyword marks the beginning of the stored procedure. The first IF statement makes sure that the year is a valid one (from 1996 to 1998). If not, it will use the year 1998. The BEGIN and END keywords mark the beginning and end of the IF block (the same block that’s delimited by the If and End If statements in VB code).

Following the IF statement is a long SELECT statement that uses the arguments passed to the stored procedure as parameters. This is a straight SQL statement that implements a parameterized query. Because the stored procedure is called like a function, it will not prompt the user for the values of the parameters; these values are passed as arguments when the stored procedure is called.

Notice that each table is assigned an alias, so that we won’t have to type the name of table over and over. The alias for the Orders table is O, the alias for the Order Details table is OD, and they’re defined in the same line that specifies the tables from which the data will come:

FROM [Order Details] OD, Orders O, Products P, Categories C

After that, we use the shorter aliases in the place of the tables’ names.

The second half of the stored procedure’s code appears in a box on the editor’s window. Rightclick anywhere in this box and select Design SQL Block. This block is a SQL statement that retrieves the total sales for the specified year and groups them by category. You can edit it either as a SQL segment or through the visual interface of the Query Builder. You already know how to handle SQL statements, so everything you learned about building SQL statements applies to stored procedures as well. The only difference is that you can embed traditional control structures, like IF statements, AND loops, and WHILE loops, and mix them with SQL.

The stored procedure we examined returns a cursor (a set of rows). It is also possible to write stored procedures that return one or more values, through their parameters list. A stored procedure that returns the total items of specific product sold in a period need not return a cursor; all we need is an integer value. You’ll see later how to return a few parameters from a stored procedure.

For now, let’s test the stored procedure. Right-click anywhere in the SQL Builder panes and select Run Stored Procedure. A dialog box pops up and prompts you to enter the values for the two parameters the query expects: the name of the category and the year. Enter Beverages and 1997 as shown in Figure 20.19 and then click OK. The stored procedure will return the qualifying rows, which will be displayed in the Output window.

Figure 20.19

Supplying the values of a stored procedure’s parameters

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

920 Chapter 20 DATABASES: ARCHITECTURE AND BASIC CONCEPTS

The SalesByCategory stored procedure returned the following lines when executed with the parameters shown in Figure 20.19. These lines appear in the Output window.

ProductName

TotalPurchase

----------------------------------------

---------------------

Chai

4887

Chang

7039

Chartreuse verte

4476

Côte de Blaye

49198

Guaraná Fantástica

1630

Ipoh Coffee

11070

Lakkalikööri

7379

Laughing Lumberjack Lager

910

Outback Lager

5468

Rhönbräu Klosterbier

4486

Sasquatch Ale

2107

Steeleye Stout

5275

No more results.

 

(12 row(s) returned)

 

@RETURN_VALUE = 0

 

Finished running dbo.”SalesByCategory”.

 

This is quite a statement, but stored procedures are not difficult to design with the SQL Builder. Let’s build a new stored procedure to calculate the number of orders placed by each customer and the total revenue they generated.

Let’s create a new stored procedure. Right-click the Stored Procedures item in the Server Explorer and select New Stored Procedure. You will see a new pane on the Designer surface with the following text, which is the outline of a stored procedure:

CREATE PROCEDURE dbo.StoredProcedure1 /*

(

@parameter1 datatype = default value, @parameter2 datatype OUTPUT

)

*/ AS

/* SET NOCOUNT ON */ RETURN

The symbols /* and */ delimit a section with comments in T-SQL. In the first commented section, you see how the stored procedure’s variables must be declared. You must replace this section with the declarations of your stored procedure’s arguments.

Then comes the AS keyword, where you must enter the SQL statements you want to execute in your stored procedure. The last statement, RETURN, is optional, because the stored procedure will terminate as soon as it reaches the last line. Use the RETURN statement to exit the stored procedure prematurely.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

STORED PROCEDURES 921

Select all the text on the editor and replace it with the following stored procedure declaration:

CREATE PROCEDURE dbo.OrdersPerCustomer @CustomerID nchar(5)=’ALFKI’

AS

The first line declares the name of the procedure. The next few lines declare the name and type of the parameters expected by the procedure. If the parameter has a default value, this is also specified on the same line as the parameter’s declaration. The OrdersPerCustomer stored procedure accepts a single argument, which is the customer’s ID (a five-character string, as you recall from the overview of the Northwind database earlier in this chapter).

Following the AS keyword is the SQL code that retrieves data from the database. Right-click anywhere in the editor’s window, and from the context menu, select Insert SQL. This will open the SQL Builder, where you can build a SQL statement with point-and-click operations.

Let’s build a SQL statement that will retrieve the orders for a specific customer. On the SQL Builder window, clear the SQL pane, then right-click the Diagram pane and select Add Table from the context menu. In the dialog box, select Customers, then click Add. Do the same for the Orders table, and then close the Add Table dialog box. You have two tables in the upper pane, and the SQL Builder inserted a line between them. This is a relation. It indicates that the CustomerID field in the Orders is the same as one of the CustomerID fields in the Customers table.

Next we must specify what we want to see in our query. Click CompanyName in the first table and OrderID in the second table (and clear all other fields). Column (field) names will appear in the grid under the Column heading, and the corresponding tables they came from will appear under the Table heading. The check mark in the output column denotes that they will be included in the output. If you run the query now (choosing Run from the context menu), you’ll see the all the orders for all customers. We don’t want all the orders per customer, just the count of the orders placed by a single customer.

Go to the OrderID row and GroupBy column in the Grid pane. When you select the cell, a button will appear with a down arrow. Click the button and a list of options will appear. Select the Count option. The Alias cell in the same row has become Expr1. This is the header of the Count column; all other columns in the query are named after the table column. Change Expr1 to Total Orders.

At this point, the statement is:

SELECT

dbo.Customers.CompanyName,

 

COUNT(dbo.Orders.OrderID) AS [Total Orders]

FROM

dbo.Customers INNER JOIN dbo.Orders ON

 

dbo.Customers.CustomerID = dbo.Orders.CustomerID

Now go to the CompanyName row, and in the GroupBy cell, select Group By. We want to count all the orders per customer, so we must first group the customers and then sum their orders. This is one of the fine points in SQL. If you make a mistake and forget to group the query appropriately, the following message will appear when you attempt to execute it:

Column Customers.CompanyName is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

922 Chapter 20 DATABASES: ARCHITECTURE AND BASIC CONCEPTS

The final SQL statement is:

SELECT

dbo.Customers.CompanyName,

 

COUNT(dbo.Orders.OrderID) AS [Total Orders]

FROM

dbo.Customers INNER JOIN dbo.Orders ON

 

dbo.Customers.CustomerID = dbo.Orders.CustomerID

GROUP BY dbo.Customers.CompanyName

If you execute it now, you’ll get a list of customers and the number of orders per customer.

Alfreds Futterkiste

6

Ana Trujillo Emparedados y helados

4

Antonio Moreno Taquería

7

Around the Horn

13

Berglunds snabbköp

18

Blauer See Delikatessen

7

This is not the stored procedure you’re executing. You’re still working with SQL Builder. When you close the SQL Builder window later, the SQL statement will be placed in the SQL box in the stored procedure’s definition.

The last step is to limit the number of customers. Add the CustomerID to the list of columns by checking the box in front of its name in the Customers table. When it’s added to grid, clear the check mark in the Output cell. We’ll use this field to limit our selection, but we don’t want it to

appear along with the other fields in the output list. Then go to the Criteria cell of the same row and enter =?. The equal sign means that we want to select the customer with a specific value of the CustomerID field, which will be supplied as argument to the stored procedure. The specific value is the question mark, which means a user-supplied value. If you entered the string “=‘ALFKI’”, the query would always return the number of orders placed by the customer Alfreds Futterkiste. It makes more sense to write parameterized queries, which can select different rows every time you execute them depending on the parameter value.

Run the query. You’ll be prompted to enter the ID of a customer. Enter ALFKI, and you’ll see the total number of orders for the specified customer in the Results pane. Now close the SQL Builder window and the SQL statement will appear in the SQL block of the stored procedure. The complete stored procedure is now:

CREATE PROCEDURE dbo.OrdersPerCustomer @CustomerID nchar(5)= ‘ALFKI’

AS

SELECT dbo.Customers.CompanyName, COUNT(dbo.Orders.OrderID) AS [Total Orders], dbo.Customers.CustomerID

FROM dbo.Customers INNER JOIN dbo.Orders ON dbo.Customers.CustomerID = dbo.Orders.CustomerID

GROUP BY dbo.Customers.CompanyName, dbo.Customers.CustomerID

HAVING (dbo.Customers.CustomerID = @CustomerID)

Notice that the question mark in the SQL statement was replaced by the first argument of the stored procedure. If there were another parameter (another question mark in the stored procedure),

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

STORED PROCEDURES 923

it would be replaced by the second argument of the stored procedure, and so on. You may have to edit the names of the arguments—you will have to do so if the order of the SQL statement’s parameters doesn’t match the order in which the arguments are passed to the stored procedure. Close the stored procedure’s window by clicking the Close button (the little X mark) in its top-right corner. Then select the name of the new stored procedure in the Stored Procedures branch of the tree in the Server Explorer and select Run Stored Procedure. You’ll be prompted to enter a customer ID. If you enter ALFKI, you’ll see the following in the Output window:

Running dbo.”OrdersPerCustomer”

( @CustomerID = ALFKI ).CompanyName Total Orders CustomerID

---------------------------------------------------------------

Alfreds Futterkiste 6 ALFKI No more results.

(1 row(s) returned) @RETURN_VALUE = 0

Finished running dbo.”OrdersPerCustomer”.

If the Output window is not visible, open it with View Output Window to see the output of the stored procedure.

The stored procedure we’ve built, which is basically a SQL statement that retrieves information from the database packaged as a procedure (so that you can call it by name), is a fairly complicated one. Stored procedures are made up of SQL statements and some T-SQL code. The different types of code are clearly marked on the stored procedure’s design window, and you can use the Query Builder to build and test the SQL part of the stored procedure. The rest is simple T-SQL code that sets up variables and uses traditional programming structures to perform housekeeping tasks.

As you will see in the following chapter, stored procedures can be called like functions. In addition, they can return results to the calling procedure through their arguments. The stored procedure that returns the number of invoices placed by a customer in a specified period need not return a cursor. It can return a single value. Like VB functions, stored procedures return a single value, which is the return value.

We could have assigned the number of orders to the stored procedure’s return value. Most practical stored procedures return multiple rows, and this is why I’ve shown you how to return a row, rather than a single value.

Can you edit the stored procedure so that it returns the total revenue generated by the selected customer in addition to the number of orders? You can use the Query Builder to design the query visually, or enter the following query’s definition in the SQL pane and then watch the query update the other panes:

SELECT

Customers.CompanyName, COUNT(Orders.OrderID) AS [Total Orders],

 

Customers.CustomerID,

 

SUM([Order Details].Quantity * Products.UnitPrice) AS Total

FROM

Customers INNER JOIN Orders ON

 

Customers.CustomerID = Orders.CustomerID INNER JOIN [Order Details]

 

ON Orders.OrderID = [Order Details].OrderID INNER JOIN Products

 

ON [Order Details].ProductID = Products.ProductID

GROUP BY

Customers.CompanyName, Customers.CustomerID

HAVING

(Customers.CustomerID = @CustomerID)

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com