Stored procedures are pre-compiled SQL programs stored in the database. As a QA engineer, you will test procedures written by developers, debug broken ones, and sometimes write your own for test data generation. These 3 challenges cover all three scenarios.
Create a stored procedure that takes a customer_id and returns their complete profile: name, city, total orders, total spending, average order value, and last order date.
DELIMITER //
CREATE PROCEDURE GetCustomerSummary(IN p_customer_id INT)
BEGIN
SELECT
c.customer_id,
CONCAT(c.first_name, ' ', c.last_name) AS full_name,
c.email,
c.city,
c.signup_date,
COUNT(o.order_id) AS total_orders,
COALESCE(SUM(CASE WHEN o.status = 'completed'
THEN o.total_amount ELSE 0 END), 0) AS total_spent,
COALESCE(ROUND(AVG(CASE WHEN o.status = 'completed'
THEN o.total_amount END), 2), 0) AS avg_order_value,
MAX(o.order_date) AS last_order_date,
DATEDIFF(CURDATE(), MAX(o.order_date)) AS days_since_last_order
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE c.customer_id = p_customer_id
GROUP BY c.customer_id, c.first_name, c.last_name,
c.email, c.city, c.signup_date;
END //
DELIMITER ;
-- Test it
CALL GetCustomerSummary(1);
CALL GetCustomerSummary(15);Call with a valid customer_id (1-15). Verify all columns return correct data.
Call with a customer who has no orders. Verify counts are 0 and dates are NULL.
Call with customer_id = 999 (does not exist). Verify it returns an empty result set.
Call with customer_id = NULL. Check if it handles gracefully or errors.
Cross-verify totals manually: SUM the orders for customer 1 and compare with procedure output.
A developer wrote this procedure to calculate order discounts. Your job is to test it thoroughly. The rules: orders over Rs 50,000 get 10% off, over Rs 10,000 get 5% off, rest get no discount.
DELIMITER //
CREATE PROCEDURE CalculateDiscount(
IN p_order_amount DECIMAL(10,2),
OUT p_discount_pct DECIMAL(5,2),
OUT p_discount_amount DECIMAL(10,2),
OUT p_final_amount DECIMAL(10,2)
)
BEGIN
IF p_order_amount > 50000 THEN
SET p_discount_pct = 10.00;
ELSEIF p_order_amount > 10000 THEN
SET p_discount_pct = 5.00;
ELSE
SET p_discount_pct = 0.00;
END IF;
SET p_discount_amount = ROUND(p_order_amount * p_discount_pct / 100, 2);
SET p_final_amount = p_order_amount - p_discount_amount;
END //
DELIMITER ;
-- TEST CASES: Run each and verify the output
-- TC1: Above 50000 — should get 10%
CALL CalculateDiscount(75000.00, @pct, @disc, @final);
SELECT @pct AS discount_pct, @disc AS discount_amt, @final AS final_amt;
-- Expected: 10.00, 7500.00, 67500.00
-- TC2: Exactly 50000 — boundary test (should get 5%, NOT 10%)
CALL CalculateDiscount(50000.00, @pct, @disc, @final);
SELECT @pct AS discount_pct, @disc AS discount_amt, @final AS final_amt;
-- Expected: 5.00, 2500.00, 47500.00
-- TC3: Between 10001 and 49999 — should get 5%
CALL CalculateDiscount(25000.00, @pct, @disc, @final);
SELECT @pct AS discount_pct, @disc AS discount_amt, @final AS final_amt;
-- Expected: 5.00, 1250.00, 23750.00
-- TC4: Exactly 10000 — boundary (should get 0%)
CALL CalculateDiscount(10000.00, @pct, @disc, @final);
SELECT @pct AS discount_pct, @disc AS discount_amt, @final AS final_amt;
-- Expected: 0.00, 0.00, 10000.00
-- TC5: Small amount
CALL CalculateDiscount(500.00, @pct, @disc, @final);
SELECT @pct AS discount_pct, @disc AS discount_amt, @final AS final_amt;
-- Expected: 0.00, 0.00, 500.00
-- TC6: Zero amount
CALL CalculateDiscount(0, @pct, @disc, @final);
SELECT @pct AS discount_pct, @disc AS discount_amt, @final AS final_amt;
-- Expected: 0.00, 0.00, 0.00
-- TC7: Negative amount — BUG if no validation
CALL CalculateDiscount(-5000.00, @pct, @disc, @final);
SELECT @pct AS discount_pct, @disc AS discount_amt, @final AS final_amt;
-- Expected: Should error or return 0. What actually happens?The boundary condition at exactly 50000 is where most discount bugs hide. "Greater than 50000" means 50000 itself does NOT qualify for 10%. But many developers use >= by mistake. Always test exact boundary values.
This procedure is supposed to generate a monthly sales report. It has 3 bugs. Find and fix them all.
-- BUGGY VERSION — Find 3 bugs
DELIMITER //
CREATE PROCEDURE MonthlySalesReport(
IN p_year INT,
IN p_month INT
)
BEGIN
SELECT
DATE_FORMAT(o.order_date, '%Y-%m') AS report_month,
COUNT(o.order_id) AS total_orders,
SUM(o.total_amount) AS total_revenue,
AVG(o.total_amount) AS avg_order_value,
COUNT(DISTINCT o.customer_id) AS unique_customers
FROM orders o
WHERE YEAR(o.order_date) = p_year
AND MONTH(o.order_date) = p_month;
-- Bug 1: Missing GROUP BY
-- Bug 2: No status filter — includes cancelled/returned orders in revenue
-- Bug 3: No rounding on AVG — returns 15+ decimal places
END //
DELIMITER ;-- FIXED VERSION
DELIMITER //
CREATE PROCEDURE MonthlySalesReport(
IN p_year INT,
IN p_month INT
)
BEGIN
SELECT
DATE_FORMAT(o.order_date, '%Y-%m') AS report_month,
COUNT(o.order_id) AS total_orders,
SUM(o.total_amount) AS total_revenue,
ROUND(AVG(o.total_amount), 2) AS avg_order_value, -- Fix 3: Round to 2 decimals
COUNT(DISTINCT o.customer_id) AS unique_customers
FROM orders o
WHERE YEAR(o.order_date) = p_year
AND MONTH(o.order_date) = p_month
AND o.status = 'completed' -- Fix 2: Only completed orders
GROUP BY DATE_FORMAT(o.order_date, '%Y-%m'); -- Fix 1: Add GROUP BY
END //
DELIMITER ;
-- Test the fixed version
CALL MonthlySalesReport(2024, 6);Q: How do you test a stored procedure?
A: I follow a structured approach: (1) Positive tests — call with valid inputs and verify output matches expected results. (2) Boundary tests — test exact boundary values like 0, max value, cutoff points. (3) Negative tests — pass NULL, negative numbers, non-existent IDs. (4) Empty result tests — call with filters that return zero rows. (5) Cross-verify — manually calculate the expected result with a SELECT query and compare with procedure output. (6) Performance — time the procedure with large datasets. (7) Concurrent calls — verify it handles multiple simultaneous executions without locking issues.
Key Point: Three skills for stored procedures: create them for test data generation, write test cases with boundary values, and debug broken ones by reading the logic line by line.