1. First add the Orders table to the database with the columns described in Table 10-1.
■ Caution Don’t forget to set the default of GETDATE() to the DateCreated column. Don’t forget to set OrderID as a primary key and an identity column. Leave Identity Seed and Identity Increment at their default values of 1. Remember that making a column an identity column tells the database to automat- ically generate values for it when new records are added to the table—you can’t supply your own values for that field when adding new records. The generated ID value can be found by reading the @@Identity system variable. You’ll use this when creating the CreateOrder stored procedure a bit later.
2. Add the OrderDetail table (see Table 10-2):
Table 10-1. The Orders Table
Column Name Data Type Allow Nulls Other Properties
OrderID int No Primary Key, Identity
DateCreated smalldatetime No Default: GETDATE() DateShipped smalldatetime Yes
Verified bit No Default Value or
Binding: 0
Completed bit No Default Value or
Binding: 0
Canceled bit No Default Value or
Binding: 0
Comments varchar(1000) Yes
CustomerName varchar(50) Yes CustomerEmail varchar(50) Yes ShippingAddress varchar(500) Yes
Table 10-2. The OrderDetail Table
Column Name Data Type Allow Nulls Other Properties
OrderID int No Primary Key
ProductID int No Primary Key
ProductName varchar(50) No
Quantity int No
8213592a117456a340854d18cee57603
■Caution Don’t forget to set the composite primary key formed of OrderID and ProductID.
3. Enforce the One-to-Many relationship between Orders and OrderDetail by adding a FOREIGN KEY constraint on the OrderID column in OrderDetail to reference the OrderID column in Orders. Do this by either using database diagrams or by opening the table in Database Explorer and clicking Table Designer ➤ Relationships (as you learned in Chapter 4).
■Note Although ProductID is part of the primary key in OrderDetail, you don’t place a FOREIGN KEY constraint on it (referencing the Product table), because products can change in time or can even be removed, while the existing orders should remain unchanged. ProductID is simply used to form the primary key of OrderDetail because at any given time, each product has a unique ProductID. However, the ProductID in OrderDetail is not required to point to an existing, real product.
How It Works: The Data Tables
Now that you have created the tables, take a closer look at the way they are designed.
The Orders Table
The Orders table basically contains two categories of information: data about the order (the first seven fields), and data about the customer that made the order (last three fields).
The professional way to store customer data is to use a separate table, named Customer, and reference that table from the Orders table. We chose not to take the professional approach in this chapter because at this stage of development, storing customer data is optional and is not one of our goals. Right now, we don’t need to know who bought our products, because the third-party payment processor deals with these details. You’ll create the Customer table in Chapter 12, where you add customer accounts functionality to BalloonShop.
Third-party payment processors, such as PayPal, store and manage the complete customer information, so it doesn’t need to be stored in your database as well. The CustomerName, ShippingAddress, and CustomerEmail fields have been added as optional fields that can be filled by the administrator if it’s easier to have this information at hand for certain (or all) orders. These are convenience fields that will be removed in Chapter 13 when you implement a serious scheme for storing customer details.
UnitCost money No
Subtotal No Computed Column Specification
Formula: Quantity * UnitCost Is Persisted: No
Table 10-2. The OrderDetail Table (Continued)
Column Name Data Type Allow Nulls Other Properties
Now take a look at the other fields. OrderID is the primary key of the table, and is an identity column so you won’t need to bother to find new IDs when adding new orders. DateCreated also has a pretty obvious role—you need to know the date when each order was created. This column has a default value of GETDATE(), which means that it will be automatically filled with the current date when adding new records if a specific value is not specified.
DateShipped is populated with the date an order has been shipped.
Three bit fields show the status of the order: Verified, Completed, and Canceled. These fields store 0 for No and 1 for Yes. If your business grows, you’ll need to extend this system to a professional order pipeline, which you’ll learn how to do in Chapter 13. For now, these three bit fields will do the job.
The Verified field is set to 1 after the payment has been confirmed by the payment processor. The site adminis- trator marks the order as verified upon receipt of the payment confirmation mail. After the payment is confirmed, the products are shipped, so the DateShipped field is populated and the Completed bit is also set to 1.
The administrator might want to mark an order as canceled (by setting the Canceled bit to 1) if it hasn’t been verified in a certain amount of time or for other various reasons. The Comments field is used to record whatever special information might show up about the order.
The OrderDetail Table
Let’s see now what information the OrderDetail table contains. Figure 10-2 shows what some typical OrderDetail records look like.
Figure 10-2. Sample data in OrderDetail
Each record in OrderDetail represents an ordered product that belongs to the order specified by OrderID. The primary key is formed by both OrderID and ProductID because a particular product can be ordered only once in one order. A Quantity field contains the number of ordered items, so it wouldn’t make any sense to have one ProductID recorded more than once for one order.
You might be wondering why apart from the product ID, you also store the price and product name in the OrderDetail table. The question is valid because if you have the product ID, you can get all the product’s details from the Product table without having any duplicated information.
The reason is this: The actual order detail data is stored in the ProductName, Quantity, and UnitCost fields. You can’t rely on ProductID to store order data, because product IDs, names, and prices can change in time. ProductID is simply used to form the primary key (in this role, it saves you from needing to create another primary key field) and is also useful because it’s the only programmatic way to link back to the original product (if the product still exists).
The last and most interesting column in OrderDetail is Subtotal, which represents the quantity multiplied by the unit price. Because it isn’t persisted, this column doesn’t occupy any disk space, and most importantly, it is always in sync with the other fields.
Creating Orders in the Database
Here you’ll write the CreateOrder stored procedure, which takes the products from a shopping cart and creates an order with them. This procedure gets called when the customer decides that he wants to buy the products in the shopping cart and clicks the Proceed to Checkout button.
Creating a new order implies adding a new record to the Orders table and a number of records (one record for each product) to the OrderDetail table.
Add the CreateOrder stored procedure to the BalloonShop database, and then we’ll talk a bit more about it:
CREATE PROCEDURE CreateOrder (@CartID char(36))
AS
/* Insert a new record into Orders */
DECLARE @OrderID int
INSERT INTO Orders DEFAULT VALUES /* Save the new Order ID */
SET @OrderID = @@IDENTITY
/* Add the order details to OrderDetail */
INSERT INTO OrderDetail
(OrderID, ProductID, ProductName, Quantity, UnitCost) SELECT
@OrderID, Product.ProductID, Product.Name, ShoppingCart.Quantity, Product.Price FROM Product JOIN ShoppingCart
ON Product.ProductID = ShoppingCart.ProductID WHERE ShoppingCart.CartID = @CartID
/* Clear the shopping cart */
DELETE FROM ShoppingCart WHERE CartID = @CartID /* Return the Order ID */
SELECT @OrderID
The procedure starts by creating the new record in the Orders table. As you can see, when adding a new record, you don’t specify any column values, as some of them allow NULLs, while the others have default values specified.
After adding the new record, you need to read the @@Identity system variable (which represents the order ID that was just generated) to a local variable named @OrderID:
/* Insert a new record into Orders*/
DECLARE @OrderID int
INSERT INTO Orders DEFAULT VALUES /* Obtain the new Order ID */
SET @OrderID = @@IDENTITY
This is the standard mechanism of extracting the newly generated ID. You must save the value of @@IDENTITY immediately after the INSERT statement, because its value is lost afterward.
Using the @OrderID variable, you add the OrderDetail records by gathering information from the Product and ShoppingCart tables. From ShoppingCart, you need the list of the products and their quantities, and from Product, you get their names and prices.
After creating the order, the visitor’s shopping cart is emptied. The last step for the CreateOrder stored procedure is to return the OrderID to the calling function. This is required when providing the order number to the customer.