
- •Using Your Sybex Electronic Book
- •Acknowledgments
- •Contents at a Glance
- •Introduction
- •Who Should Read This Book?
- •How About the Advanced Topics?
- •The Structure of the Book
- •How to Reach the Author
- •The Integrated Development Environment
- •The Start Page
- •Project Types
- •Your First VB Application
- •Making the Application More Robust
- •Making the Application More User-Friendly
- •The IDE Components
- •The IDE Menu
- •The Toolbox Window
- •The Solution Explorer
- •The Properties Window
- •The Output Window
- •The Command Window
- •The Task List Window
- •Environment Options
- •A Few Common Properties
- •A Few Common Events
- •A Few Common Methods
- •Building a Console Application
- •Summary
- •Building a Loan Calculator
- •How the Loan Application Works
- •Designing the User Interface
- •Programming the Loan Application
- •Validating the Data
- •Building a Math Calculator
- •Designing the User Interface
- •Programming the MathCalculator App
- •Adding More Features
- •Exception Handling
- •Taking the LoanCalculator to the Web
- •Working with Multiple Forms
- •Working with Multiple Projects
- •Executable Files
- •Distributing an Application
- •VB.NET at Work: Creating a Windows Installer
- •Finishing the Windows Installer
- •Running the Windows Installer
- •Verifying the Installation
- •Summary
- •Variables
- •Declaring Variables
- •Types of Variables
- •Converting Variable Types
- •User-Defined Data Types
- •Examining Variable Types
- •Why Declare Variables?
- •A Variable’s Scope
- •The Lifetime of a Variable
- •Constants
- •Arrays
- •Declaring Arrays
- •Initializing Arrays
- •Array Limits
- •Multidimensional Arrays
- •Dynamic Arrays
- •Arrays of Arrays
- •Variables as Objects
- •So, What’s an Object?
- •Formatting Numbers
- •Formatting Dates
- •Flow-Control Statements
- •Test Structures
- •Loop Structures
- •Nested Control Structures
- •The Exit Statement
- •Summary
- •Modular Coding
- •Subroutines
- •Functions
- •Arguments
- •Argument-Passing Mechanisms
- •Event-Handler Arguments
- •Passing an Unknown Number of Arguments
- •Named Arguments
- •More Types of Function Return Values
- •Overloading Functions
- •Summary
- •The Appearance of Forms
- •Properties of the Form Control
- •Placing Controls on Forms
- •Setting the TabOrder
- •VB.NET at Work: The Contacts Project
- •Anchoring and Docking
- •Loading and Showing Forms
- •The Startup Form
- •Controlling One Form from within Another
- •Forms vs. Dialog Boxes
- •VB.NET at Work: The MultipleForms Project
- •Designing Menus
- •The Menu Editor
- •Manipulating Menus at Runtime
- •Building Dynamic Forms at Runtime
- •The Form.Controls Collection
- •VB.NET at Work: The DynamicForm Project
- •Creating Event Handlers at Runtime
- •Summary
- •The TextBox Control
- •Basic Properties
- •Text-Manipulation Properties
- •Text-Selection Properties
- •Text-Selection Methods
- •Undoing Edits
- •VB.NET at Work: The TextPad Project
- •Capturing Keystrokes
- •The ListBox, CheckedListBox, and ComboBox Controls
- •Basic Properties
- •The Items Collection
- •VB.NET at Work: The ListDemo Project
- •Searching
- •The ComboBox Control
- •The ScrollBar and TrackBar Controls
- •The ScrollBar Control
- •The TrackBar Control
- •Summary
- •The Common Dialog Controls
- •Using the Common Dialog Controls
- •The Color Dialog Box
- •The Font Dialog Box
- •The Open and Save As Dialog Boxes
- •The Print Dialog Box
- •The RichTextBox Control
- •The RTF Language
- •Methods
- •Advanced Editing Features
- •Cutting and Pasting
- •Searching in a RichTextBox Control
- •Formatting URLs
- •VB.NET at Work: The RTFPad Project
- •Summary
- •What Is a Class?
- •Building the Minimal Class
- •Adding Code to the Minimal Class
- •Property Procedures
- •Customizing Default Members
- •Custom Enumerations
- •Using the SimpleClass in Other Projects
- •Firing Events
- •Shared Properties
- •Parsing a Filename String
- •Reusing the StringTools Class
- •Encapsulation and Abstraction
- •Inheritance
- •Inheriting Existing Classes
- •Polymorphism
- •The Shape Class
- •Object Constructors and Destructors
- •Instance and Shared Methods
- •Who Can Inherit What?
- •Parent Class Keywords
- •Derived Class Keyword
- •Parent Class Member Keywords
- •Derived Class Member Keyword
- •MyBase and MyClass
- •Summary
- •On Designing Windows Controls
- •Enhancing Existing Controls
- •Building the FocusedTextBox Control
- •Building Compound Controls
- •VB.NET at Work: The ColorEdit Control
- •VB.NET at Work: The Label3D Control
- •Raising Events
- •Using the Custom Control in Other Projects
- •VB.NET at Work: The Alarm Control
- •Designing Irregularly Shaped Controls
- •Designing Owner-Drawn Menus
- •Designing Owner-Drawn ListBox Controls
- •Using ActiveX Controls
- •Summary
- •Programming Word
- •Objects That Represent Text
- •The Documents Collection and the Document Object
- •Spell-Checking Documents
- •Programming Excel
- •The Worksheets Collection and the Worksheet Object
- •The Range Object
- •Using Excel as a Math Parser
- •Programming Outlook
- •Retrieving Information
- •Recursive Scanning of the Contacts Folder
- •Summary
- •Advanced Array Topics
- •Sorting Arrays
- •Searching Arrays
- •Other Array Operations
- •Array Limitations
- •The ArrayList Collection
- •Creating an ArrayList
- •Adding and Removing Items
- •The HashTable Collection
- •VB.NET at Work: The WordFrequencies Project
- •The SortedList Class
- •The IEnumerator and IComparer Interfaces
- •Enumerating Collections
- •Custom Sorting
- •Custom Sorting of a SortedList
- •The Serialization Class
- •Serializing Individual Objects
- •Serializing a Collection
- •Deserializing Objects
- •Summary
- •Handling Strings and Characters
- •The Char Class
- •The String Class
- •The StringBuilder Class
- •VB.NET at Work: The StringReversal Project
- •VB.NET at Work: The CountWords Project
- •Handling Dates
- •The DateTime Class
- •The TimeSpan Class
- •VB.NET at Work: Timing Operations
- •Summary
- •Accessing Folders and Files
- •The Directory Class
- •The File Class
- •The DirectoryInfo Class
- •The FileInfo Class
- •The Path Class
- •VB.NET at Work: The CustomExplorer Project
- •Accessing Files
- •The FileStream Object
- •The StreamWriter Object
- •The StreamReader Object
- •Sending Data to a File
- •The BinaryWriter Object
- •The BinaryReader Object
- •VB.NET at Work: The RecordSave Project
- •The FileSystemWatcher Component
- •Properties
- •Events
- •VB.NET at Work: The FileSystemWatcher Project
- •Summary
- •Displaying Images
- •The Image Object
- •Exchanging Images through the Clipboard
- •Drawing with GDI+
- •The Basic Drawing Objects
- •Drawing Shapes
- •Drawing Methods
- •Gradients
- •Coordinate Transformations
- •Specifying Transformations
- •VB.NET at Work: Plotting Functions
- •Bitmaps
- •Specifying Colors
- •Defining Colors
- •Processing Bitmaps
- •Summary
- •The Printing Objects
- •PrintDocument
- •PrintDialog
- •PageSetupDialog
- •PrintPreviewDialog
- •PrintPreviewControl
- •Printer and Page Properties
- •Page Geometry
- •Printing Examples
- •Printing Tabular Data
- •Printing Plain Text
- •Printing Bitmaps
- •Using the PrintPreviewControl
- •Summary
- •Examining the Advanced Controls
- •How Tree Structures Work
- •The ImageList Control
- •The TreeView Control
- •Adding New Items at Design Time
- •Adding New Items at Runtime
- •Assigning Images to Nodes
- •Scanning the TreeView Control
- •The ListView Control
- •The Columns Collection
- •The ListItem Object
- •The Items Collection
- •The SubItems Collection
- •Summary
- •Types of Errors
- •Design-Time Errors
- •Runtime Errors
- •Logic Errors
- •Exceptions and Structured Exception Handling
- •Studying an Exception
- •Getting a Handle on this Exception
- •Finally (!)
- •Customizing Exception Handling
- •Throwing Your Own Exceptions
- •Debugging
- •Breakpoints
- •Stepping Through
- •The Local and Watch Windows
- •Summary
- •Basic Concepts
- •Recursion in Real Life
- •A Simple Example
- •Recursion by Mistake
- •Scanning Folders Recursively
- •Describing a Recursive Procedure
- •Translating the Description to Code
- •The Stack Mechanism
- •Stack Defined
- •Recursive Programming and the Stack
- •Passing Arguments through the Stack
- •Special Issues in Recursive Programming
- •Knowing When to Use Recursive Programming
- •Summary
- •MDI Applications: The Basics
- •Building an MDI Application
- •Built-In Capabilities of MDI Applications
- •Accessing Child Forms
- •Ending an MDI Application
- •A Scrollable PictureBox
- •Summary
- •What Is a Database?
- •Relational Databases
- •Exploring the Northwind Database
- •Exploring the Pubs Database
- •Understanding Relations
- •The Server Explorer
- •Working with Tables
- •Relationships, Indices, and Constraints
- •Structured Query Language
- •Executing SQL Statements
- •Selection Queries
- •Calculated Fields
- •SQL Joins
- •Action Queries
- •The Query Builder
- •The Query Builder Interface
- •SQL at Work: Calculating Sums
- •SQL at Work: Counting Rows
- •Limiting the Selection
- •Parameterized Queries
- •Calculated Columns
- •Specifying Left, Right, and Inner Joins
- •Stored Procedures
- •Summary
- •How About XML?
- •Creating a DataSet
- •The DataGrid Control
- •Data Binding
- •VB.NET at Work: The ViewEditCustomers Project
- •Binding Complex Controls
- •Programming the DataAdapter Object
- •The Command Objects
- •The Command and DataReader Objects
- •VB.NET at Work: The DataReader Project
- •VB.NET at Work: The StoredProcedure Project
- •Summary
- •The Structure of a DataSet
- •Navigating the Tables of a DataSet
- •Updating DataSets
- •The DataForm Wizard
- •Handling Identity Fields
- •Transactions
- •Performing Update Operations
- •Updating Tables Manually
- •Building and Using Custom DataSets
- •Summary
- •An HTML Primer
- •HTML Code Elements
- •Server-Client Interaction
- •The Structure of HTML Documents
- •URLs and Hyperlinks
- •The Basic HTML Tags
- •Inserting Graphics
- •Tables
- •Forms and Controls
- •Processing Requests on the Server
- •Building a Web Application
- •Interacting with a Web Application
- •Maintaining State
- •The Web Controls
- •The ASP.NET Objects
- •The Page Object
- •The Response Object
- •The Request Object
- •The Server Object
- •Using Cookies
- •Handling Multiple Forms in Web Applications
- •Summary
- •The Data-Bound Web Controls
- •Simple Data Binding
- •Binding to DataSets
- •Is It a Grid, or a Table?
- •Getting Orders on the Web
- •The Forms of the ProductSearch Application
- •Paging Large DataSets
- •Customizing the Appearance of the DataGrid Control
- •Programming the Select Button
- •Summary
- •How to Serve the Web
- •Building a Web Service
- •Consuming the Web Service
- •Maintaining State in Web Services
- •A Data-Driven Web Service
- •Consuming the Products Web Service in VB
- •Summary

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 |