Comprehensive Guide to SQL Server Stored Procedures Using an E-Commerce Database
Introduction
Stored procedures in SQL Server (T-SQL) are powerful, precompiled, server-side scripts that encapsulate business logic, improve query performance, and enhance security by reducing client-side code. This comprehensive guide uses the ECommerceDB database (from the provided T-SQL script with 11 tables and 20 rows each, as of September 8, 2025) to explore stored procedures in detail. We cover creation, input/output parameters, execution (including storing outputs in variables), complex operations, transactions, error handling, real-world e-commerce scenarios, use cases, best practices, and common pitfalls. The guide progresses through five complexity levels—Primary, Basic, Intermediate, Advanced, and Expert—each with multiple examples, detailed explanations, execution steps, and practical applications. Additionally, we include 10 scenario-based assignment questions and answers for trainers to test applicants, ensuring hands-on learning.
The ECommerceDB includes tables like Customers, Orders, Products, Order_Items, Payments, Sellers, Categories, Shipments, Reviews, Addresses, and Coupons, with constraints (e.g., PRIMARY KEY, FOREIGN KEY, CHECK) and sample data (e.g., 20 orders with total revenue of 2728.00). Scenarios are tailored to e-commerce tasks like order processing, inventory management, and customer analytics. All scripts are user-friendly, executable in SQL Server Management Studio (SSMS), and follow industry standards. Keywords include CREATE PROCEDURE, @parameters, IN/OUT, EXEC, BEGIN TRANSACTION, COMMIT, ROLLBACK, TRY-CATCH, ERROR_MESSAGE, and more. The content is SEO-optimized for terms like "T-SQL stored procedure tutorial," "SQL Server e-commerce examples," and "SQL transaction error handling."
E-Commerce Database Context
The ECommerceDB (created via ECommerceDB.sql) models an e-commerce platform with 11 tables:
- Customers: Stores user data (e.g., customer_id, username, email, is_active).
- Sellers: Manages vendor info (e.g., seller_id, business_name, rating).
- Categories: Organizes products (e.g., category_id, name like 'Electronics').
- Products: Holds item details (e.g., product_id, name, price, stock).
- Orders: Tracks purchases (e.g., order_id, customer_id, total_amount, status).
- Order_Items: Links orders to products (e.g., item_id, order_id, quantity).
- Payments: Records payments (e.g., payment_id, order_id, method).
- Shipments: Manages shipping (e.g., shipment_id, tracking_number).
- Reviews: Stores product feedback (e.g., review_id, rating, comment).
- Addresses: Holds customer addresses (e.g., address_id, type, street).
- Coupons: Manages discounts (e.g., coupon_id, code, discount_percentage).
Each table has 20 rows with varied data (e.g., order dates in August 2025, statuses like 'Pending', 'Delivered'). Constraints ensure data integrity (e.g., CHECK for rating BETWEEN 1 AND 5, UNIQUE for email). This setup supports realistic scenarios like checkout processing, inventory updates, and sales reporting.
Understanding Stored Procedures
A stored procedure is a named collection of T-SQL statements stored in the database, compiled once, and reusable for tasks like data manipulation, validation, or reporting. Benefits include:
- Performance: Precompiled execution plans reduce runtime overhead.
- Security: Restrict direct table access, allowing parameterized queries.
- Reusability: Encapsulate logic for consistent use across applications.
- Maintainability: Centralize code for easy updates.
Stored procedures are ideal for e-commerce tasks like processing orders, updating inventory, or generating reports. They support parameters (input/output), transactions for atomicity, and error handling for robustness. Common pitfalls include poor parameter validation, transaction mismanagement, and ignoring performance impacts (e.g., excessive locking).
Stored Procedure Complexity Levels: Step-by-Step Examples
Primary Level: Basic Creation and Execution
Purpose: Introduce stored procedure creation and execution without parameters, focusing on simple queries.
Example 1: List Active Customers
Scenario: Generate a list of active customers for a weekly marketing email campaign.
-- Create a procedure to list active customers
CREATE PROCEDURE GetActiveCustomers
AS
BEGIN
SET NOCOUNT ON;
SELECT customer_id, username, email, first_name, last_name
FROM Customers
WHERE is_active = 1
ORDER BY username;
END;
GO
-- Execute the procedure
EXEC GetActiveCustomers;
Detailed Explanation:
- Purpose: Retrieves active customers (is_active = 1) for marketing, sorted by username for readability.
- Code Breakdown:
- CREATE PROCEDURE GetActiveCustomers: Defines the procedure name.
- SET NOCOUNT ON: Suppresses row count messages to reduce network traffic.
- BEGIN/END: Encapsulates the query for clarity.
- SELECT ... WHERE is_active = 1: Filters active customers (all 20 in sample data).
- ORDER BY username: Ensures consistent output.
- EXEC GetActiveCustomers: Runs the procedure.
- Execution Steps:
- Ensure ECommerceDB is active (USE ECommerceDB).
- Run the CREATE PROCEDURE script in SSMS.
- Execute with EXEC GetActiveCustomers.
- Storing Output in a Variable:
This stores results in #TempCustomers for further processing (e.g., exporting to a CRM).tsql
-- Store output in a temporary table CREATE TABLE #TempCustomers ( customer_id INT, username VARCHAR(50), email VARCHAR(100), first_name VARCHAR(50), last_name VARCHAR(50) ); INSERT INTO #TempCustomers EXEC GetActiveCustomers; SELECT * FROM #TempCustomers; DROP TABLE #TempCustomers;
- Output: 20 rows (e.g., 1, john_doe, john@example.com, John, Doe).
- Keywords: CREATE PROCEDURE, SET NOCOUNT, BEGIN, END, SELECT, WHERE, ORDER BY, EXEC, INSERT.
- Challenges:
- Forgetting GO after CREATE PROCEDURE causes syntax errors.
- Misspelling table/column names (e.g., Customer vs Customers).
- Not understanding BIT type for is_active.
- Use Case: Automate weekly marketing reports, feeding customer data to email systems.
- Best Practices:
- Use SET NOCOUNT ON to improve performance.
- Include ORDER BY for predictable results.
- Comment code for clarity (e.g., -- Marketing report).
- Test with SELECT * FROM sys.procedures to verify creation.
- Variations:
- Filter by preferred_language (e.g., WHERE preferred_language = 'English').
- Add columns like registration_date for segmentation.
- Error Handling: Minimal at this level, but ensure table exists to avoid “Invalid object name” errors.
Why Primary: This is the simplest form, encapsulating a single query without parameters, ideal for beginners learning procedure syntax and execution.
Example 2: List Products by Category
Scenario: Display all products in a specific category (hard-coded) for a category page.
-- Create a procedure to list products in Electronics category
CREATE PROCEDURE GetElectronicsProducts
AS
BEGIN
SET NOCOUNT ON;
SELECT p.product_id, p.name, p.price, p.stock
FROM Products p
INNER JOIN Categories c ON p.category_id = c.category_id
WHERE c.name = 'Electronics';
END;
GO
-- Execute the procedure
EXEC GetElectronicsProducts;
-- Store output in a variable (table)
CREATE TABLE #ElectronicsProducts (
product_id INT,
name VARCHAR(100),
price DECIMAL(10,2),
stock INT
);
INSERT INTO #ElectronicsProducts
EXEC GetElectronicsProducts;
SELECT * FROM #ElectronicsProducts;
DROP TABLE #ElectronicsProducts;
Detailed Explanation:
- Purpose: Lists products in the 'Electronics' category for a website category page.
- Code Breakdown:
- INNER JOIN: Links Products and Categories on category_id.
- WHERE c.name = 'Electronics': Filters to one category.
- SET NOCOUNT ON: Optimizes performance.
- EXEC GetElectronicsProducts: Runs the query.
- Temporary table stores output for further use (e.g., API response).
- Execution Steps:
- Run CREATE PROCEDURE in SSMS.
- Execute with EXEC GetElectronicsProducts.
- Use the temporary table script to capture output.
- Output: 1 row (e.g., 1, Laptop, 1200.00, 50).
- Keywords: CREATE PROCEDURE, SET NOCOUNT, BEGIN, END, SELECT, INNER JOIN, WHERE, EXEC, INSERT.
- Challenges:
- Ensuring correct JOIN syntax to avoid Cartesian products.
- Handling case sensitivity in WHERE c.name = 'Electronics'.
- Verifying category_id FK integrity.
- Use Case: Populate e-commerce category pages or inventory reports.
- Best Practices:
- Use specific column names (not *) for clarity.
- Validate category existence before execution.
- Store in a schema (e.g., dbo.GetElectronicsProducts).
- Variations:
- Include description or image_url for richer output.
- Filter by is_available = 1 for active products.
- Error Handling: Check for category existence to avoid empty results.
Basic Level: Input Parameters
Purpose: Introduce input parameters for dynamic queries.
Example 3: Get Orders by Customer
Scenario: Retrieve all orders for a specific customer to display their order history.
-- Create a procedure with an input parameter
CREATE PROCEDURE GetCustomerOrders
@CustomerID INT
AS
BEGIN
SET NOCOUNT ON;
SELECT order_id, order_date, total_amount, status
FROM Orders
WHERE customer_id = @CustomerID
ORDER BY order_date DESC;
END;
GO
-- Execute with different inputs
EXEC GetCustomerOrders @CustomerID = 1; -- Orders for john_doe
EXEC GetCustomerOrders @CustomerID = 2; -- Orders for jane_smith
-- Store output in a variable (table)
CREATE TABLE #CustomerOrders (
order_id INT,
order_date DATETIME,
total_amount DECIMAL(10,2),
status VARCHAR(20)
);
INSERT INTO #CustomerOrders
EXEC GetCustomerOrders @CustomerID = 1;
SELECT * FROM #CustomerOrders;
DROP TABLE #CustomerOrders;
-- Basic error handling
DECLARE @CustomerID INT = 999; -- Non-existent ID
IF NOT EXISTS (SELECT 1 FROM Customers WHERE customer_id = @CustomerID)
BEGIN
RAISERROR ('Customer ID %d does not exist', 16, 1, @CustomerID);
RETURN;
END
EXEC GetCustomerOrders @CustomerID;
Detailed Explanation:
- Purpose: Displays a customer’s order history for a user portal, filtered by customer_id.
- Code Breakdown:
- @CustomerID INT: Input parameter for dynamic filtering.
- WHERE customer_id = @CustomerID: Filters orders.
- ORDER BY order_date DESC: Shows recent orders first.
- RAISERROR: Basic error handling for invalid IDs.
- Temporary table captures output for further processing.
- Execution Steps:
- Create the procedure in SSMS.
- Execute with EXEC GetCustomerOrders @CustomerID = 1.
- Use the temporary table script to store results.
- Test error handling with an invalid @CustomerID.
- Output: For @CustomerID = 1, 1 row (e.g., 1, 2025-08-01, 1200.00, Delivered).
- Keywords: CREATE PROCEDURE, @parameter, SET NOCOUNT, SELECT, WHERE, ORDER BY, EXEC, RAISERROR, IF, INSERT.
- Challenges:
- Validating @CustomerID against Customers to avoid empty results.
- Parameter type mismatches (e.g., passing a string).
- Handling FOREIGN KEY constraints.
- Use Case: Customer order history in an e-commerce dashboard.
- Best Practices:
- Validate input parameters to prevent invalid queries.
- Use descriptive parameter names (e.g., @CustomerID vs @ID).
- Include ORDER BY for user-friendly output.
- Variations:
- Add a date range parameter (e.g., @StartDate, @EndDate).
- Include Order_Items via JOIN for detailed order data.
- Error Handling: Check for customer existence to avoid silent failures.
Example 4: Filter Products by Price Range
Scenario: List products within a specified price range for a filtered product search.
-- Create a procedure with multiple input parameters
CREATE PROCEDURE GetProductsByPrice
@MinPrice DECIMAL(10,2),
@MaxPrice DECIMAL(10,2)
AS
BEGIN
SET NOCOUNT ON;
IF @MinPrice < 0 OR @MaxPrice < @MinPrice
BEGIN
RAISERROR ('Invalid price range: MinPrice=%d, MaxPrice=%d', 16, 1, @MinPrice, @MaxPrice);
RETURN;
END
SELECT product_id, name, price, stock
FROM Products
WHERE price BETWEEN @MinPrice AND @MaxPrice
ORDER BY price;
END;
GO
-- Execute with different inputs
EXEC GetProductsByPrice @MinPrice = 10.00, @MaxPrice = 50.00;
EXEC GetProductsByPrice @MinPrice = 0.00, @MaxPrice = 100.00;
-- Store output in a variable (table)
CREATE TABLE #PriceFilteredProducts (
product_id INT,
name VARCHAR(100),
price DECIMAL(10,2),
stock INT
);
INSERT INTO #PriceFilteredProducts
EXEC GetProductsByPrice @MinPrice = 10.00, @MaxPrice = 50.00;
SELECT * FROM #PriceFilteredProducts;
DROP TABLE #PriceFilteredProducts;
-- Test error handling
EXEC GetProductsByPrice @MinPrice = 100.00, @MaxPrice = 50.00; -- Invalid range
Detailed Explanation:
- Purpose: Filters products for a price-based search feature on an e-commerce site.
- Code Breakdown:
- @MinPrice, @MaxPrice: Input parameters for price range.
- IF ... RAISERROR: Validates price range (non-negative, logical order).
- WHERE price BETWEEN ...: Filters products inclusively.
- Temporary table stores results for further use (e.g., JSON export).
- Execution Steps:
- Create the procedure.
- Run with valid inputs (e.g., 10.00, 50.00).
- Capture output in a temporary table.
- Test error handling with invalid inputs.
- Output: For @MinPrice = 10.00, @MaxPrice = 50.00, 13 rows (e.g., 2, T-Shirt, 20.00, 100).
- Keywords: CREATE PROCEDURE, @parameter, SET NOCOUNT, IF, RAISERROR, SELECT, WHERE, BETWEEN, ORDER BY, EXEC, INSERT.
- Challenges:
- Ensuring valid price range to avoid logical errors.
- Handling DECIMAL precision in comparisons.
- Avoiding empty results for narrow ranges.
- Use Case: Price filter on product search pages.
- Best Practices:
- Validate parameter logic early.
- Use BETWEEN for readable range queries.
- Store results for integration with APIs.
- Variations:
- Add @CategoryID to filter by category.
- Include is_available = 1 for active products.
- Error Handling: Validate input ranges to prevent invalid queries.
Intermediate Level: Input/Output Parameters and Simple Operations
Purpose: Use input and output parameters to perform data modifications and return results.
Example 5: Add New Order
Scenario: Insert a new order and return the generated order ID for further processing.
-- Create a procedure with input and output parameters
CREATE PROCEDURE AddNewOrder
@CustomerID INT,
@TotalAmount DECIMAL(10,2),
@ShippingAddress VARCHAR(255),
@BillingAddress VARCHAR(255),
@NewOrderID INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM Customers WHERE customer_id = @CustomerID)
BEGIN
RAISERROR ('Invalid Customer ID %d', 16, 1, @CustomerID);
RETURN;
END
IF @TotalAmount <= 0
BEGIN
RAISERROR ('Total amount must be positive: %d', 16, 1, @TotalAmount);
RETURN;
END
INSERT INTO Orders (customer_id, total_amount, shipping_address, billing_address, status)
VALUES (@CustomerID, @TotalAmount, @ShippingAddress, @BillingAddress, 'Pending');
SET @NewOrderID = SCOPE_IDENTITY();
END;
GO
-- Execute and capture output
DECLARE @OrderID INT;
EXEC AddNewOrder
@CustomerID = 2,
@TotalAmount = 150.00,
@ShippingAddress = '456 Apt Rd',
@BillingAddress = '456 Apt Rd',
@NewOrderID = @OrderID OUTPUT;
SELECT @OrderID AS NewOrderID;
-- Test error handling
DECLARE @ErrorOrderID INT;
EXEC AddNewOrder
@CustomerID = 999, -- Invalid ID
@TotalAmount = 150.00,
@ShippingAddress = '456 Apt Rd',
@BillingAddress = '456 Apt Rd',
@NewOrderID = @ErrorOrderID OUTPUT;
Detailed Explanation:
- Purpose: Creates a new order for a customer and returns the order_id for adding items or payments.
- Code Breakdown:
- @CustomerID, @TotalAmount, etc.: Input parameters for order details.
- @NewOrderID INT OUTPUT: Returns the new order_id.
- IF NOT EXISTS: Validates customer existence.
- IF @TotalAmount <= 0: Ensures valid amount.
- INSERT INTO Orders: Adds the order with Pending status.
- SCOPE_IDENTITY(): Captures the new order_id.
- Execution Steps:
- Create the procedure in SSMS.
- Declare @OrderID to store output.
- Execute with valid inputs (e.g., @CustomerID = 2).
- Test with invalid @CustomerID to trigger error.
- Output: NewOrderID = 21 (next ID after 20 orders).
- Keywords: CREATE PROCEDURE, @parameter, OUTPUT, SET NOCOUNT, IF, RAISERROR, INSERT, SCOPE_IDENTITY, EXEC, SELECT.
- Challenges:
- Ensuring @CustomerID satisfies FOREIGN KEY constraint.
- Capturing OUTPUT parameter correctly.
- Handling invalid inputs (e.g., negative @TotalAmount).
- Use Case: Order creation during checkout, returning ID for subsequent steps.
- Best Practices:
- Validate all inputs to prevent constraint violations.
- Use SCOPE_IDENTITY() for reliable ID retrieval.
- Return meaningful error messages.
- Variations:
- Add @Status parameter with CHECK validation.
- Include discount_applied in the insert.
- Error Handling: Checks for customer existence and valid amounts to avoid FK or logical errors.
Example 6: Update Product Stock
Scenario: Update product stock after a manual inventory adjustment and return the new stock level.
-- Create a procedure to update stock and return new value
CREATE PROCEDURE UpdateProductStock
@ProductID INT,
@StockAdjustment INT,
@NewStock INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM Products WHERE product_id = @ProductID)
BEGIN
RAISERROR ('Invalid Product ID %d', 16, 1, @ProductID);
RETURN;
END
UPDATE Products
SET stock = stock + @StockAdjustment,
@NewStock = stock + @StockAdjustment
WHERE product_id = @ProductID;
IF @NewStock < 0
BEGIN
RAISERROR ('Stock cannot be negative for Product ID %d', 16, 1, @ProductID);
RETURN;
END
END;
GO
-- Execute and capture output
DECLARE @NewStockLevel INT;
EXEC UpdateProductStock
@ProductID = 1,
@StockAdjustment = -5,
@NewStock = @NewStockLevel OUTPUT;
SELECT @NewStockLevel AS NewStock;
-- Test error handling
DECLARE @ErrorStock INT;
EXEC UpdateProductStock
@ProductID = 1,
@StockAdjustment = -100, -- Would make stock negative
@NewStock = @ErrorStock OUTPUT;
Detailed Explanation:
- Purpose: Adjusts product stock (e.g., after restocking or returns) and returns the new stock level.
- Code Breakdown:
- @ProductID, @StockAdjustment: Inputs for product and adjustment amount.
- @NewStock INT OUTPUT: Returns updated stock.
- IF NOT EXISTS: Validates product.
- UPDATE ... SET stock = ... , @NewStock = ...: Updates and captures new stock.
- IF @NewStock < 0: Prevents negative stock (aligned with CHECK constraint).
- Execution Steps:
- Create the procedure.
- Declare @NewStockLevel for output.
- Execute with valid adjustment (e.g., -5 for Laptop).
- Test with invalid adjustment (e.g., -100) to trigger error.
- Output: NewStock = 45 (Laptop stock from 50 to 45).
- Keywords: CREATE PROCEDURE, @parameter, OUTPUT, SET NOCOUNT, IF, RAISERROR, UPDATE, EXEC, SELECT.
- Challenges:
- Aligning with CHECK (stock >= 0) constraint.
- Capturing output in UPDATE statement.
- Handling concurrent updates.
- Use Case: Inventory adjustments in e-commerce (e.g., restocking, returns).
- Best Practices:
- Validate inputs against constraints.
- Use output parameters for feedback.
- Check post-update conditions (e.g., negative stock).
- Variations:
- Add @Reason parameter for audit logging.
- Include updated_at update in Products.
- Error Handling: Prevents invalid products and negative stock, aligning with DB constraints.
Advanced Level: Complex Operations and Transactions
Purpose: Incorporate multi-table updates, transactions, and basic error handling.
Example 7: Add Order Item with Stock Update
Scenario: Add an order item and update product stock in a single transaction.
-- Create a procedure with transaction for order item and stock
CREATE PROCEDURE AddOrderItem
@OrderID INT,
@ProductID INT,
@Quantity INT,
@PricePerUnit DECIMAL(10,2)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
BEGIN TRY
-- Validate inputs
IF NOT EXISTS (SELECT 1 FROM Orders WHERE order_id = @OrderID)
BEGIN
RAISERROR ('Invalid Order ID %d', 16, 1, @OrderID);
RETURN;
END
IF NOT EXISTS (SELECT 1 FROM Products WHERE product_id = @ProductID AND stock >= @Quantity)
BEGIN
RAISERROR ('Invalid Product ID %d or insufficient stock', 16, 1, @ProductID);
RETURN;
END
IF @Quantity <= 0
BEGIN
RAISERROR ('Quantity must be positive: %d', 16, 1, @Quantity);
RETURN;
END
-- Update product stock
UPDATE Products
SET stock = stock - @Quantity
WHERE product_id = @ProductID;
-- Insert order item
INSERT INTO Order_Items (order_id, product_id, quantity, price_per_unit, subtotal)
VALUES (@OrderID, @ProductID, @Quantity, @PricePerUnit, @Quantity * @PricePerUnit);
COMMIT;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
DECLARE @ErrorMsg NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR (@ErrorMsg, 16, 1);
END CATCH;
END;
GO
-- Execute the procedure
EXEC AddOrderItem
@OrderID = 1,
@ProductID = 2,
@Quantity = 2,
@PricePerUnit = 20.00;
-- Test error handling
EXEC AddOrderItem
@OrderID = 999, -- Invalid order
@ProductID = 2,
@Quantity = 2,
@PricePerUnit = 20.00;
Detailed Explanation:
- Purpose: Adds an order item and reduces stock atomically, ensuring inventory consistency during checkout.
- Code Breakdown:
- @OrderID, @ProductID, etc.: Inputs for order item details.
- BEGIN TRANSACTION: Ensures atomicity.
- BEGIN TRY/CATCH: Catches errors (e.g., FK violations).
- IF NOT EXISTS: Validates order and product with sufficient stock.
- UPDATE Products: Reduces stock.
- INSERT Order_Items: Adds item with calculated subtotal.
- COMMIT: Finalizes transaction.
- ROLLBACK in CATCH: Undoes changes on error.
- Execution Steps:
- Create the procedure.
- Execute with valid inputs (e.g., order_id = 1, product_id = 2).
- Test with invalid @OrderID to trigger error.
- Output: 1 row inserted into Order_Items, T-Shirt stock reduced from 100 to 98.
- Keywords: CREATE PROCEDURE, @parameter, SET NOCOUNT, BEGIN TRANSACTION, COMMIT, ROLLBACK, BEGIN TRY, BEGIN CATCH, RAISERROR, ERROR_MESSAGE, IF, UPDATE, INSERT.
- Challenges:
- Ensuring transaction scope (all or nothing).
- Handling CHECK constraints (e.g., quantity > 0).
- Managing concurrent stock updates (e.g., use UPDLOCK).
- Use Case: Adding items to a cart during checkout.
- Best Practices:
- Use transactions for multi-table updates.
- Validate all inputs before modifications.
- Capture errors with TRY-CATCH for user feedback.
- Variations:
- Add @Discount parameter for subtotal calculation.
- Update Orders.total_amount dynamically.
- Error Handling: Robust validation and TRY-CATCH handle FK, stock, and quantity errors.
Example 8: Apply Coupon to Order
Scenario: Apply a coupon to an order and update the discount, ensuring the coupon is valid.
-- Create a procedure to apply a coupon
CREATE PROCEDURE ApplyCoupon
@OrderID INT,
@CouponCode VARCHAR(20),
@DiscountApplied DECIMAL(10,2) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
BEGIN TRY
-- Validate inputs
IF NOT EXISTS (SELECT 1 FROM Orders WHERE order_id = @OrderID)
BEGIN
RAISERROR ('Invalid Order ID %d', 16, 1, @OrderID);
RETURN;
END
IF NOT EXISTS (SELECT 1 FROM Coupons WHERE code = @CouponCode AND is_active = 1 AND GETDATE() BETWEEN start_date AND end_date)
BEGIN
RAISERROR ('Invalid or expired Coupon Code %s', 16, 1, @CouponCode);
RETURN;
END
-- Calculate discount
DECLARE @DiscountPercentage DECIMAL(5,2);
SELECT @DiscountPercentage = discount_percentage
FROM Coupons
WHERE code = @CouponCode;
UPDATE Orders
SET discount_applied = total_amount * (@DiscountPercentage / 100),
@DiscountApplied = total_amount * (@DiscountPercentage / 100)
WHERE order_id = @OrderID;
-- Update coupon usage
UPDATE Coupons
SET used_count = used_count + 1
WHERE code = @CouponCode;
COMMIT;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
DECLARE @ErrorMsg NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR (@ErrorMsg, 16, 1);
END CATCH;
END;
GO
-- Execute and capture output
DECLARE @Discount DECIMAL(10,2);
EXEC ApplyCoupon
@OrderID = 3,
@CouponCode = 'SUMMER15',
@DiscountApplied = @Discount OUTPUT;
SELECT @Discount AS DiscountApplied;
-- Test error handling
EXEC ApplyCoupon
@OrderID = 3,
@CouponCode = 'EXPIRED'; -- Invalid coupon
Detailed Explanation:
- Purpose: Applies a coupon to an order, calculates the discount, and tracks coupon usage.
- Code Breakdown:
- @OrderID, @CouponCode: Inputs for order and coupon.
- @DiscountApplied OUTPUT: Returns the discount amount.
- IF NOT EXISTS: Validates order and active coupon within date range.
- DECLARE @DiscountPercentage: Stores coupon discount rate.
- UPDATE Orders: Applies discount based on total_amount.
- UPDATE Coupons: Increments used_count.
- TRY-CATCH: Handles errors like invalid coupons.
- Execution Steps:
- Create the procedure.
- Execute with valid @OrderID and @CouponCode (e.g., SUMMER15).
- Capture @DiscountApplied.
- Test with invalid coupon code.
- Output: For order_id = 3, DiscountApplied = 2.25 (15% of 15.00).
- Keywords: CREATE PROCEDURE, @parameter, OUTPUT, SET NOCOUNT, BEGIN TRANSACTION, COMMIT, ROLLBACK, BEGIN TRY, BEGIN CATCH, RAISERROR, ERROR_MESSAGE, IF, UPDATE, SELECT.
- Challenges:
- Validating coupon date range and activity.
- Calculating discount correctly.
- Handling concurrent coupon usage.
- Use Case: Coupon application during checkout.
- Best Practices:
- Validate date ranges dynamically.
- Use transactions for multi-table updates.
- Return calculated values via output parameters.
- Variations:
- Check usage_limit in Coupons.
- Update Orders.total_amount after discount.
- Error Handling: Ensures valid order and coupon, rolls back on failure.
Expert Level: Complex Operations, Transactions, and Robust Error Handling
Purpose: Handle complex multi-table operations, full transaction management, and advanced error handling.
Example 9: Process Complete Order
Scenario: Process a full order (insert order, order items, payment, update stock) with comprehensive validation.
-- Create a procedure for complete order processing
CREATE PROCEDURE ProcessOrder
@CustomerID INT,
@ProductID INT,
@Quantity INT,
@PricePerUnit DECIMAL(10,2),
@ShippingAddress VARCHAR(255),
@BillingAddress VARCHAR(255),
@PaymentMethod VARCHAR(20),
@CardLastFour VARCHAR(4),
@CVV VARCHAR(3),
@BillingEmail VARCHAR(100),
@NewOrderID INT OUTPUT,
@ErrorMessage NVARCHAR(4000) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- Validate inputs
IF NOT EXISTS (SELECT 1 FROM Customers WHERE customer_id = @CustomerID)
BEGIN
SET @ErrorMessage = 'Invalid Customer ID ' + CAST(@CustomerID AS VARCHAR(10));
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
IF NOT EXISTS (SELECT 1 FROM Products WHERE product_id = @ProductID AND stock >= @Quantity)
BEGIN
SET @ErrorMessage = 'Invalid Product ID or insufficient stock for Product ID ' + CAST(@ProductID AS VARCHAR(10));
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
IF @Quantity <= 0
BEGIN
SET @ErrorMessage = 'Quantity must be positive: ' + CAST(@Quantity AS VARCHAR(10));
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
IF @PaymentMethod NOT IN ('Credit Card', 'PayPal', 'Bank Transfer')
BEGIN
SET @ErrorMessage = 'Invalid Payment Method: ' + @PaymentMethod;
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
IF LEN(@CVV) != 3
BEGIN
SET @ErrorMessage = 'Invalid CVV length';
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
-- Insert new order
INSERT INTO Orders (customer_id, total_amount, shipping_address, billing_address, status)
VALUES (@CustomerID, @Quantity * @PricePerUnit, @ShippingAddress, @BillingAddress, 'Pending');
SET @NewOrderID = SCOPE_IDENTITY();
-- Insert order item
INSERT INTO Order_Items (order_id, product_id, quantity, price_per_unit, subtotal)
VALUES (@NewOrderID, @ProductID, @Quantity, @PricePerUnit, @Quantity * @PricePerUnit);
-- Update product stock
UPDATE Products
SET stock = stock - @Quantity
WHERE product_id = @ProductID;
-- Insert payment
INSERT INTO Payments (order_id, amount, method, status, transaction_id, card_last_four, cvv, billing_email)
VALUES (@NewOrderID, @Quantity * @PricePerUnit, @PaymentMethod, 'Pending', 'TX' + CAST(@NewOrderID AS VARCHAR(10)), @CardLastFour, @CVV, @BillingEmail);
COMMIT;
SET @ErrorMessage = NULL;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
SET @ErrorMessage = ERROR_MESSAGE();
RAISERROR (@ErrorMessage, 16, 1);
END CATCH;
END;
GO
-- Execute and capture output
DECLARE @OrderID INT, @Error NVARCHAR(4000);
EXEC ProcessOrder
@CustomerID = 1,
@ProductID = 2,
@Quantity = 3,
@PricePerUnit = 20.00,
@ShippingAddress = '123 Home St',
@BillingAddress = '123 Home St',
@PaymentMethod = 'Credit Card',
@CardLastFour = '1234',
@CVV = '123',
@BillingEmail = 'john@example.com',
@NewOrderID = @OrderID OUTPUT,
@ErrorMessage = @Error OUTPUT;
SELECT @OrderID AS NewOrderID, @Error AS ErrorMessage;
-- Test error handling
EXEC ProcessOrder
@CustomerID = 999, -- Invalid customer
@ProductID = 2,
@Quantity = 3,
@PricePerUnit = 20.00,
@ShippingAddress = '123 Home St',
@BillingAddress = '123 Home St',
@PaymentMethod = 'Credit Card',
@CardLastFour = '1234',
@CVV = '123',
@BillingEmail = 'john@example.com',
@NewOrderID = @OrderID OUTPUT,
@ErrorMessage = @Error OUTPUT;
Detailed Explanation:
- Purpose: Processes a complete e-commerce order, updating Orders, Order_Items, Products, and Payments atomically.
- Code Breakdown:
- Multiple input parameters for order details.
- @NewOrderID, @ErrorMessage OUTPUT: Return order ID and error details.
- Extensive validation for customer, product, quantity, payment method, and CVV.
- BEGIN TRY/CATCH: Captures errors (e.g., FK violations, CHECK constraints).
- BEGIN TRANSACTION: Ensures all operations succeed or fail together.
- INSERT Orders, Order_Items, Payments: Adds order data.
- UPDATE Products: Reduces stock.
- COMMIT/ROLLBACK: Manages transaction outcome.
- Execution Steps:
- Create the procedure.
- Declare variables for outputs.
- Execute with valid inputs.
- Test with invalid inputs (e.g., @CustomerID = 999).
- Output: NewOrderID = 21, ErrorMessage = NULL (success); or error message for failures.
- Keywords: CREATE PROCEDURE, @parameter, OUTPUT, SET NOCOUNT, BEGIN TRY, BEGIN CATCH, BEGIN TRANSACTION, COMMIT, ROLLBACK, RAISERROR, ERROR_MESSAGE, IF, INSERT, UPDATE, SCOPE_IDENTITY.
- Challenges:
- Coordinating multi-table updates.
- Handling CHECK constraints (e.g., cvv length, status values).
- Managing transaction isolation for concurrency.
- Use Case: Full checkout process in e-commerce.
- Best Practices:
- Validate all inputs against constraints.
- Use TRY-CATCH for robust error handling.
- Consider locking (e.g., UPDLOCK) for stock updates.
- Variations:
- Add coupon application logic.
- Include Shipments insertion.
- Error Handling: Comprehensive validation and error capture ensure robust execution.
Example 10: Process Bulk Order with Multiple Items
Scenario: Process an order with multiple items, updating stock and payments, with detailed error logging.
-- Create a procedure for bulk order processing
CREATE PROCEDURE ProcessBulkOrder
@CustomerID INT,
@ShippingAddress VARCHAR(255),
@BillingAddress VARCHAR(255),
@PaymentMethod VARCHAR(20),
@CardLastFour VARCHAR(4),
@CVV VARCHAR(3),
@BillingEmail VARCHAR(100),
@NewOrderID INT OUTPUT,
@ErrorMessage NVARCHAR(4000) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- Validate customer and payment
IF NOT EXISTS (SELECT 1 FROM Customers WHERE customer_id = @CustomerID)
BEGIN
SET @ErrorMessage = 'Invalid Customer ID ' + CAST(@CustomerID AS VARCHAR(10));
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
IF @PaymentMethod NOT IN ('Credit Card', 'PayPal', 'Bank Transfer')
BEGIN
SET @ErrorMessage = 'Invalid Payment Method: ' + @PaymentMethod;
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
IF LEN(@CVV) != 3
BEGIN
SET @ErrorMessage = 'Invalid CVV length';
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
-- Create a temporary table for items
CREATE TABLE #OrderItems (
ProductID INT,
Quantity INT,
PricePerUnit DECIMAL(10,2)
);
-- Example: Insert sample items (in real use, passed via table-valued parameter)
INSERT INTO #OrderItems (ProductID, Quantity, PricePerUnit)
VALUES (1, 2, 1200.00), (2, 3, 20.00);
-- Validate items
IF EXISTS (
SELECT 1
FROM #OrderItems oi
LEFT JOIN Products p ON oi.ProductID = p.product_id
WHERE p.product_id IS NULL OR p.stock < oi.Quantity OR oi.Quantity <= 0
)
BEGIN
SET @ErrorMessage = 'Invalid items or insufficient stock';
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
-- Calculate total amount
DECLARE @TotalAmount DECIMAL(10,2);
SELECT @TotalAmount = SUM(Quantity * PricePerUnit)
FROM #OrderItems;
-- Insert order
INSERT INTO Orders (customer_id, total_amount, shipping_address, billing_address, status)
VALUES (@CustomerID, @TotalAmount, @ShippingAddress, @BillingAddress, 'Pending');
SET @NewOrderID = SCOPE_IDENTITY();
-- Insert order items
INSERT INTO Order_Items (order_id, product_id, quantity, price_per_unit, subtotal)
SELECT @NewOrderID, ProductID, Quantity, PricePerUnit, Quantity * PricePerUnit
FROM #OrderItems;
-- Update product stock
UPDATE Products
SET stock = stock - oi.Quantity
FROM Products p
INNER JOIN #OrderItems oi ON p.product_id = oi.ProductID;
-- Insert payment
INSERT INTO Payments (order_id, amount, method, status, transaction_id, card_last_four, cvv, billing_email)
VALUES (@NewOrderID, @TotalAmount, @PaymentMethod, 'Pending', 'TX' + CAST(@NewOrderID AS VARCHAR(10)), @CardLastFour, @CVV, @BillingEmail);
DROP TABLE #OrderItems;
COMMIT;
SET @ErrorMessage = NULL;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
SET @ErrorMessage = ERROR_MESSAGE();
RAISERROR (@ErrorMessage, 16, 1);
END CATCH;
END;
GO
-- Execute and capture output
DECLARE @OrderID INT, @Error NVARCHAR(4000);
EXEC ProcessBulkOrder
@CustomerID = 1,
@ShippingAddress = '123 Home St',
@BillingAddress = '123 Home St',
@PaymentMethod = 'Credit Card',
@CardLastFour = '1234',
@CVV = '123',
@BillingEmail = 'john@example.com',
@NewOrderID = @OrderID OUTPUT,
@ErrorMessage = @Error OUTPUT;
SELECT @OrderID AS NewOrderID, @Error AS ErrorMessage;
-- Test error handling
EXEC ProcessBulkOrder
@CustomerID = 1,
@ShippingAddress = '123 Home St',
@BillingAddress = '123 Home St',
@PaymentMethod = 'Invalid', -- Invalid method
@CardLastFour = '1234',
@CVV = '123',
@BillingEmail = 'john@example.com',
@NewOrderID = @OrderID OUTPUT,
@ErrorMessage = @Error OUTPUT;
Detailed Explanation:
- Purpose: Processes an order with multiple items, updating stock and payments in a single transaction.
- Code Breakdown:
- Inputs for customer, payment, and order details; outputs for order_id and errors.
- #OrderItems: Temporary table for multiple items (simulating a cart).
- Extensive validation for customer, payment, and items.
- SELECT SUM: Calculates total order amount.
- Inserts into Orders, Order_Items, Payments; updates Products.
- TRY-CATCH and ROLLBACK for error handling.
- Execution Steps:
- Create the procedure.
- Execute with valid inputs.
- Test with invalid payment method.
- Output: NewOrderID = 21, ErrorMessage = NULL (success).
- Keywords: CREATE PROCEDURE, @parameter, OUTPUT, SET NOCOUNT, BEGIN TRY, BEGIN CATCH, BEGIN TRANSACTION, COMMIT, ROLLBACK, RAISERROR, ERROR_MESSAGE, IF, INSERT, UPDATE, SELECT, CREATE TABLE, DROP TABLE.
- Challenges:
- Managing multiple items in a transaction.
- Validating temporary table data.
- Handling concurrency for stock updates.
- Use Case: Bulk order processing during checkout.
- Best Practices:
- Use temporary tables for bulk data.
- Validate all inputs thoroughly.
- Ensure transaction isolation (e.g., SERIALIZABLE).
- Variations:
- Use table-valued parameters for items.
- Add coupon discounts.
- Error Handling: Comprehensive validation and error capture for robustness.
10 Scenario-Based Assignment Questions and Answers
These assignments test stored procedure skills using the ECommerceDB, focusing on creation, parameters, execution, transactions, and error handling. Each includes a scenario, solution, explanation, execution, output, and use case.
Question 1: List Products by Category (Primary)
Scenario: Create a procedure to list products for a given category ID for a website category page.
CREATE PROCEDURE GetProductsByCategory
@CategoryID INT
AS
BEGIN
SET NOCOUNT ON;
SELECT p.product_id, p.name, p.price, p.stock
FROM Products p
INNER JOIN Categories c ON p.category_id = c.category_id
WHERE c.category_id = @CategoryID;
END;
GO
-- Execute
EXEC GetProductsByCategory @CategoryID = 1;
-- Store output
CREATE TABLE #CategoryProducts (
product_id INT,
name VARCHAR(100),
price DECIMAL(10,2),
stock INT
);
INSERT INTO #CategoryProducts
EXEC GetProductsByCategory @CategoryID = 1;
SELECT * FROM #CategoryProducts;
DROP TABLE #CategoryProducts;
Explanation:
- Purpose: Displays products for a category (e.g., Electronics).
- Details: Uses @CategoryID to filter products via INNER JOIN. Stores output in a temporary table.
- Execution: Run EXEC with @CategoryID = 1 (Electronics).
- Output: 1 row (e.g., 1, Laptop, 1200.00, 50).
- Use Case: Category browsing on e-commerce sites.
- Challenges: Ensuring valid @CategoryID, handling empty results.
- Best Practices: Use SET NOCOUNT ON, validate category existence.
Question 2: Add Customer (Basic)
Scenario: Insert a new customer and return their ID.
CREATE PROCEDURE AddCustomer
@Username VARCHAR(50),
@Email VARCHAR(100),
@PasswordHash VARCHAR(255),
@FirstName VARCHAR(50),
@LastName VARCHAR(50),
@NewCustomerID INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1 FROM Customers WHERE username = @Username OR email = @Email)
BEGIN
RAISERROR ('Username or email already exists', 16, 1);
RETURN;
END
INSERT INTO Customers (username, email, password_hash, first_name, last_name)
VALUES (@Username, @Email, @PasswordHash, @FirstName, @LastName);
SET @NewCustomerID = SCOPE_IDENTITY();
END;
GO
-- Execute
DECLARE @CustomerID INT;
EXEC AddCustomer
@Username = 'new_customer',
@Email = 'new@customer.com',
@PasswordHash = 'hash22',
@FirstName = 'New',
@LastName = 'Customer',
@NewCustomerID = @CustomerID OUTPUT;
SELECT @CustomerID AS NewCustomerID;
Explanation:
- Purpose: Registers a new customer.
- Details: Validates UNIQUE constraints, inserts, and returns customer_id.
- Execution: Run with unique @Username, @Email.
- Output: NewCustomerID = 21.
- Use Case: User registration.
- Challenges: Handling UNIQUE violations.
- Best Practices: Validate inputs, use SCOPE_IDENTITY().
Question 3: Update Order Status (Intermediate)
Scenario: Update an order’s status and return the new status.
CREATE PROCEDURE UpdateOrderStatus
@OrderID INT,
@NewStatus VARCHAR(20),
@CurrentStatus VARCHAR(20) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM Orders WHERE order_id = @OrderID)
BEGIN
RAISERROR ('Invalid Order ID %d', 16, 1, @OrderID);
RETURN;
END
IF @NewStatus NOT IN ('Pending', 'Shipped', 'Delivered', 'Cancelled')
BEGIN
RAISERROR ('Invalid Status %s', 16, 1, @NewStatus);
RETURN;
END
UPDATE Orders
SET status = @NewStatus,
@CurrentStatus = @NewStatus
WHERE order_id = @OrderID;
END;
GO
-- Execute
DECLARE @Status VARCHAR(20);
EXEC UpdateOrderStatus
@OrderID = 1,
@NewStatus = 'Shipped',
@CurrentStatus = @Status OUTPUT;
SELECT @Status AS CurrentStatus;
Explanation:
- Purpose: Updates order status (e.g., to 'Shipped').
- Details: Validates order and status, updates Orders.
- Execution: Run with valid @OrderID, @NewStatus.
- Output: CurrentStatus = Shipped.
- Use Case: Order tracking updates.
- Challenges: Aligning with CHECK constraint.
- Best Practices: Validate status values.
Question 4: Delete Expired Coupons (Intermediate)
Scenario: Delete expired coupons and return the count of deleted rows.
CREATE PROCEDURE DeleteExpiredCoupons
@DeletedCount INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
BEGIN TRY
DELETE FROM Coupons
WHERE end_date < GETDATE()
AND is_active = 1;
SET @DeletedCount = @@ROWCOUNT;
COMMIT;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
RAISERROR ('Error deleting coupons: %s', 16, 1, ERROR_MESSAGE());
END CATCH;
END;
GO
-- Execute
DECLARE @Count INT;
EXEC DeleteExpiredCoupons @DeletedCount = @Count OUTPUT;
SELECT @Count AS DeletedCoupons;
Explanation:
- Purpose: Cleans up expired coupons.
- Details: Deletes coupons past end_date, uses transaction, returns count.
- Execution: Run to delete expired coupons.
- Output: DeletedCoupons = 2 (EASTER10, LABORDAY15).
- Use Case: Promotion maintenance.
- Challenges: Date comparisons, transaction scope.
- Best Practices: Use @@ROWCOUNT, transactions.
Question 5: Add Review (Advanced)
Scenario: Add a product review and update product rating.
CREATE PROCEDURE AddReview
@ProductID INT,
@CustomerID INT,
@Rating INT,
@Comment TEXT,
@NewReviewID INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
BEGIN TRY
IF NOT EXISTS (SELECT 1 FROM Products WHERE product_id = @ProductID)
BEGIN
RAISERROR ('Invalid Product ID %d', 16, 1, @ProductID);
RETURN;
END
IF NOT EXISTS (SELECT 1 FROM Customers WHERE customer_id = @CustomerID)
BEGIN
RAISERROR ('Invalid Customer ID %d', 16, 1, @CustomerID);
RETURN;
END
IF @Rating NOT BETWEEN 1 AND 5
BEGIN
RAISERROR ('Invalid Rating %d', 16, 1, @Rating);
RETURN;
END
INSERT INTO Reviews (product_id, customer_id, rating, comment)
VALUES (@ProductID, @CustomerID, @Rating, @Comment);
SET @NewReviewID = SCOPE_IDENTITY();
UPDATE Products
SET rating = (SELECT AVG(CAST(rating AS FLOAT)) FROM Reviews WHERE product_id = @ProductID)
WHERE product_id = @ProductID;
COMMIT;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
RAISERROR ('Error adding review: %s', 16, 1, ERROR_MESSAGE());
END CATCH;
END;
GO
-- Execute
DECLARE @ReviewID INT;
EXEC AddReview
@ProductID = 1,
@CustomerID = 2,
@Rating = 4,
@Comment = 'Great laptop!',
@NewReviewID = @ReviewID OUTPUT;
SELECT @ReviewID AS NewReviewID;
Explanation:
- Purpose: Adds a review and updates product rating.
- Details: Validates inputs, inserts review, recalculates rating.
- Execution: Run with valid inputs.
- Output: NewReviewID = 21.
- Use Case: Customer feedback system.
- Challenges: Recalculating averages, transaction scope.
- Best Practices: Use transactions, validate CHECK constraints.
Question 6: Update Seller Rating (Advanced)
Scenario: Update a seller’s rating based on their products’ reviews.
CREATE PROCEDURE UpdateSellerRating
@SellerID INT,
@NewRating FLOAT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
BEGIN TRY
IF NOT EXISTS (SELECT 1 FROM Sellers WHERE seller_id = @SellerID)
BEGIN
RAISERROR ('Invalid Seller ID %d', 16, 1, @SellerID);
RETURN;
END
UPDATE Sellers
SET rating = (
SELECT AVG(CAST(r.rating AS FLOAT))
FROM Products p
INNER JOIN Reviews r ON p.product_id = r.product_id
WHERE p.seller_id = @SellerID
),
@NewRating = (
SELECT AVG(CAST(r.rating AS FLOAT))
FROM Products p
INNER JOIN Reviews r ON p.product_id = r.product_id
WHERE p.seller_id = @SellerID
)
WHERE seller_id = @SellerID;
IF @NewRating IS NULL
BEGIN
SET @NewRating = 0.0;
END
COMMIT;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
RAISERROR ('Error updating rating: %s', 16, 1, ERROR_MESSAGE());
END CATCH;
END;
GO
-- Execute
DECLARE @Rating FLOAT;
EXEC UpdateSellerRating
@SellerID = 1,
@NewRating = @Rating OUTPUT;
SELECT @Rating AS NewRating;
Explanation:
- Purpose: Updates seller rating based on reviews.
- Details: Calculates average review rating, updates Sellers.
- Execution: Run with valid @SellerID.
- Output: NewRating = 5.0 (for seller_id = 1).
- Use Case: Seller performance tracking.
- Challenges: Handling NULL averages, subquery accuracy.
- Best Practices: Use CAST for precision, transactions.
Question 7: Process Shipment (Advanced)
Scenario: Add a shipment for an order and update order status.
CREATE PROCEDURE AddShipment
@OrderID INT,
@Carrier VARCHAR(50),
@TrackingNumber VARCHAR(50),
@Cost DECIMAL(10,2),
@ShippedDate DATE
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
BEGIN TRY
IF NOT EXISTS (SELECT 1 FROM Orders WHERE order_id = @OrderID)
BEGIN
RAISERROR ('Invalid Order ID %d', 16, 1, @OrderID);
RETURN;
END
IF EXISTS (SELECT 1 FROM Shipments WHERE tracking_number = @TrackingNumber)
BEGIN
RAISERROR ('Tracking number %s already exists', 16, 1, @TrackingNumber);
RETURN;
END
INSERT INTO Shipments (order_id, carrier, tracking_number, cost, status, shipped_date)
VALUES (@OrderID, @Carrier, @TrackingNumber, @Cost, 'In Transit', @ShippedDate);
UPDATE Orders
SET status = 'Shipped'
WHERE order_id = @OrderID;
COMMIT;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
RAISERROR ('Error adding shipment: %s', 16, 1, ERROR_MESSAGE());
END CATCH;
END;
GO
-- Execute
EXEC AddShipment
@OrderID = 2,
@Carrier = 'FedEx',
@TrackingNumber = 'TRACK21',
@Cost = 10.00,
@ShippedDate = '2025-09-08';
Explanation:
- Purpose: Adds shipment and updates order status.
- Details: Validates order and tracking number, inserts shipment, updates status.
- Execution: Run with valid inputs.
- Output: 1 row inserted, order status updated.
- Use Case: Shipping management.
- Challenges: UNIQUE constraint on tracking_number.
- Best Practices: Use transactions, validate inputs.
Question 8: Cancel Order (Expert)
Scenario: Cancel an order, restore stock, and update payment status.
CREATE PROCEDURE CancelOrder
@OrderID INT,
@ErrorMessage NVARCHAR(4000) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION;
IF NOT EXISTS (SELECT 1 FROM Orders WHERE order_id = @OrderID)
BEGIN
SET @ErrorMessage = 'Invalid Order ID ' + CAST(@OrderID AS VARCHAR(10));
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
IF EXISTS (SELECT 1 FROM Orders WHERE order_id = @OrderID AND status = 'Delivered')
BEGIN
SET @ErrorMessage = 'Cannot cancel delivered order';
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
UPDATE Products
SET stock = stock + oi.quantity
FROM Products p
INNER JOIN Order_Items oi ON p.product_id = oi.product_id
WHERE oi.order_id = @OrderID;
UPDATE Payments
SET status = 'Failed'
WHERE order_id = @OrderID;
UPDATE Orders
SET status = 'Cancelled'
WHERE order_id = @OrderID;
COMMIT;
SET @ErrorMessage = NULL;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
SET @ErrorMessage = ERROR_MESSAGE();
RAISERROR (@ErrorMessage, 16, 1);
END CATCH;
END;
GO
-- Execute
DECLARE @Error NVARCHAR(4000);
EXEC CancelOrder
@OrderID = 3,
@ErrorMessage = @Error OUTPUT;
SELECT @Error AS ErrorMessage;
Explanation:
- Purpose: Cancels an order, restores stock, updates payment.
- Details: Validates order, prevents delivered order cancellation, updates multiple tables.
- Execution: Run with valid @OrderID.
- Output: ErrorMessage = NULL, stock and status updated.
- Use Case: Order cancellation process.
- Challenges: Multi-table updates, transaction scope.
- Best Practices: Comprehensive validation, transactions.
Question 9: Update Customer Profile (Expert)
Scenario: Update customer details and address in a single transaction.
CREATE PROCEDURE UpdateCustomerProfile
@CustomerID INT,
@Email VARCHAR(100),
@Phone VARCHAR(15),
@Street VARCHAR(255),
@City VARCHAR(50),
@State VARCHAR(50),
@ZipCode VARCHAR(10),
@Country VARCHAR(50),
@ErrorMessage NVARCHAR(4000) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION;
IF NOT EXISTS (SELECT 1 FROM Customers WHERE customer_id = @CustomerID)
BEGIN
SET @ErrorMessage = 'Invalid Customer ID ' + CAST(@CustomerID AS VARCHAR(10));
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
IF EXISTS (SELECT 1 FROM Customers WHERE email = @Email AND customer_id != @CustomerID)
BEGIN
SET @ErrorMessage = 'Email already in use';
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
UPDATE Customers
SET email = @Email,
phone = @Phone
WHERE customer_id = @CustomerID;
UPDATE Addresses
SET street = @Street,
city = @City,
state = @State,
zip_code = @ZipCode,
country = @Country
WHERE customer_id = @CustomerID AND type = 'Shipping';
COMMIT;
SET @ErrorMessage = NULL;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
SET @ErrorMessage = ERROR_MESSAGE();
RAISERROR (@ErrorMessage, 16, 1);
END CATCH;
END;
GO
-- Execute
DECLARE @Error NVARCHAR(4000);
EXEC UpdateCustomerProfile
@CustomerID = 1,
@Email = 'updated.john@example.com',
@Phone = '555-9999',
@Street = '123 New St',
@City = 'New York',
@State = 'NY',
@ZipCode = '10002',
@Country = 'USA',
@ErrorMessage = @Error OUTPUT;
SELECT @Error AS ErrorMessage;
Explanation:
- Purpose: Updates customer profile and shipping address.
- Details: Validates customer and email, updates Customers and Addresses.
- Execution: Run with valid inputs.
- Output: ErrorMessage = NULL.
- Use Case: Profile management.
- Challenges: UNIQUE email constraint, multi-table updates.
- Best Practices: Validate inputs, use transactions.
Question 10: Process Return (Expert)
Scenario: Process a return, update stock, and mark order item as returned.
CREATE PROCEDURE ProcessReturn
@OrderItemID INT,
@ReturnStatus VARCHAR(20),
@ErrorMessage NVARCHAR(4000) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION;
IF NOT EXISTS (SELECT 1 FROM Order_Items WHERE item_id = @OrderItemID)
BEGIN
SET @ErrorMessage = 'Invalid Order Item ID ' + CAST(@OrderItemID AS VARCHAR(10));
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
IF @ReturnStatus NOT IN ('Requested', 'Approved')
BEGIN
SET @ErrorMessage = 'Invalid Return Status ' + @ReturnStatus;
RAISERROR (@ErrorMessage, 16, 1);
RETURN;
END
UPDATE Products
SET stock = stock + oi.quantity
FROM Products p
INNER JOIN Order_Items oi ON p.product_id = oi.product_id
WHERE oi.item_id = @OrderItemID;
UPDATE Order_Items
SET return_status = @ReturnStatus
WHERE item_id = @OrderItemID;
COMMIT;
SET @ErrorMessage = NULL;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK;
SET @ErrorMessage = ERROR_MESSAGE();
RAISERROR (@ErrorMessage, 16, 1);
END CATCH;
END;
GO
-- Execute
DECLARE @Error NVARCHAR(4000);
EXEC ProcessReturn
@OrderItemID = 1,
@ReturnStatus = 'Approved',
@ErrorMessage = @Error OUTPUT;
SELECT @Error AS ErrorMessage;
Explanation:
- Purpose: Processes a product return, restoring stock.
- Details: Validates item and status, updates Products and Order_Items.
- Execution: Run with valid @OrderItemID.
- Output: ErrorMessage = NULL.
- Use Case: Return processing.
- Challenges: CHECK constraint on return_status, transaction scope.
- Best Practices: Validate status, use transactions.
Best Practices and Standards
- Naming: Use clear names (e.g., AddOrderItem vs Proc1).
- SET NOCOUNT ON: Reduce network traffic.
- Validation: Check inputs against constraints (FK, CHECK, UNIQUE).
- Transactions: Use for multi-table updates to ensure atomicity.
- Error Handling: Implement TRY-CATCH, return meaningful @ErrorMessage.
- Output Parameters: Use for feedback (e.g., IDs, counts).
- Concurrency: Consider UPDLOCK or SERIALIZABLE for stock updates.
- Documentation: Comment code, document parameters in SSMS.
- Testing: Test edge cases (e.g., invalid IDs, zero quantities).
- Performance: Avoid cursors, use set-based operations.
Execution Notes
- Run ECommerceDB.sql first to create the database.
- Execute procedures in SSMS with USE ECommerceDB.
- Test with varied inputs to verify error handling.
- Use temporary tables or variables to capture outputs.
- Check sys.procedures for procedure existence.
Conclusion
This guide provides a thorough exploration of stored procedures, from basic queries to complex multi-table operations, using the ECommerceDB. The 10 examples and 10 assignments cover real-world e-commerce scenarios, ensuring practical learning. The progression from Primary to Expert levels builds skills systematically, with robust error handling and transactions for reliability. Trainers can use the assignments to test applicants, while developers can apply these patterns to e-commerce systems. For further exploration, consider table-valued parameters, logging errors to a table, or integrating with external APIs.
No comments:
Post a Comment
Thanks for your valuable comment...........
Md. Mominul Islam