Database Technologies
Stored Procedures, Parameters and Flow Control
PGCP-AC
1. Stored Programs
A stored procedure is a named program stored in the database and invoked with CALL. It can accept parameters, declare local variables, execute SQL, use conditional and loop statements, return result sets and coordinate transactional work.
Stored procedures are useful for operations that belong close to shared data: controlled updates, data validation, administrative processing and a stable database API for several clients.
They are not automatically faster or easier to maintain than application code. Version control, deployment, testing, observability and portability must be planned.
2. Creating a Procedure
DELIMITER //
CREATE PROCEDURE count_products(OUT p_total BIGINT)
BEGIN
SELECT COUNT(*)
INTO p_total
FROM product;
END//
DELIMITER ;
CALL count_products(@total);
SELECT @total;
The procedure assigns the row count to its OUT parameter. The caller supplies a session variable beginning with @ to receive the value.
3. The Client Delimiter
Statements inside a stored-program body end with semicolons. An interactive client that normally sends text at the first semicolon would split the CREATE PROCEDURE definition prematurely.
DELIMITER changes which token that client recognizes as the end of the complete definition. It is a client command, not part of the stored procedure and not a server-side transaction statement.
Application drivers can usually send the complete definition through their APIs without embedding an interactive delimiter command.
4. Calling and Removing Procedures
CALL invokes a procedure:
CALL find_customer_orders(42);
A procedure may return zero or more result sets in addition to OUT or INOUT values. Client code must consume all results according to its database driver.
DROP PROCEDURE removes a definition:
DROP PROCEDURE IF EXISTS find_customer_orders;
CREATE OR REPLACE support varies by stored-object type and MySQL version, so deployments often use explicit drop-and-create migrations.
5. IN Parameters
IN is the default parameter mode and supplies a value to the procedure:
CREATE PROCEDURE list_orders(IN p_customer_id BIGINT)
BEGIN
SELECT order_id, ordered_at, total_amount
FROM order_header
WHERE customer_id = p_customer_id
ORDER BY ordered_at DESC, order_id DESC;
END;
Assignments inside the procedure do not return a changed IN value to the caller. Prefixing parameters with p_ helps distinguish them from columns.
6. OUT Parameters
An OUT parameter carries a result back:
CREATE PROCEDURE customer_order_count(
IN p_customer_id BIGINT,
OUT p_order_count BIGINT
)
BEGIN
SELECT COUNT(*)
INTO p_order_count
FROM order_header
WHERE customer_id = p_customer_id;
END;
The caller provides a variable capable of receiving the output. Its incoming value is not the procedure's input contract.
OUT parameters suit small fixed result sets. A SELECT result set is often clearer for tabular output.
7. INOUT Parameters
An INOUT parameter carries an initial value into the routine and a final value out:
CREATE PROCEDURE add_tax(
INOUT p_amount DECIMAL(12, 2),
IN p_rate DECIMAL(6, 4)
)
BEGIN
SET p_amount = ROUND(p_amount * (1 + p_rate), 2);
END;
The caller initializes a session variable, passes it to CALL and reads the changed value afterward.
Use INOUT when modifying one logical caller value is natural. Separate IN and OUT parameters can be clearer when input and result have different meanings.
8. Compound Statements
BEGIN and END form a compound statement containing declarations and executable statements:
BEGIN
DECLARE v_count BIGINT DEFAULT 0;
SELECT COUNT(*) INTO v_count FROM product;
SET p_total = v_count;
END
Nested blocks create nested scope. Local variables cease to exist when their block ends.
Declarations must appear before executable statements in a block and follow MySQL's required declaration ordering among variables, conditions, cursors and handlers.
9. Local and Session Variables
Local variables are declared with DECLARE and exist only within their block. Parameters are also local to the routine invocation.
Session user variables use @name and remain associated with the connection:
SET @customer_id = 42;
CALL customer_order_count(@customer_id, @count);
Connection pools can reuse sessions, so application logic should not assume session variables begin empty. Prefer parameterized calls and explicit initialization.
Local variables are usually safer for routine implementation because their scope and type are declared.
10. Assignment
SET performs direct assignment:
SET v_subtotal = v_quantity * v_price;
SELECT ... INTO assigns query columns:
SELECT customer_name, status
INTO v_name, v_status
FROM customer
WHERE customer_id = p_customer_id;
The query must have the expected cardinality. Multiple rows cause an error. No rows raises a not-found condition that can be handled or prevented through an aggregate or existence check.
11. Naming and Scope
Ambiguous names can silently refer to a parameter or column differently than intended:
WHERE customer_id = customer_id
This might become a tautology rather than a parameter comparison. Use conventions and aliases:
WHERE c.customer_id = p_customer_id
Qualify columns in multi-table statements. Clear naming prevents logic defects that remain syntactically valid.
12. IF Statements
IF p_amount < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Amount must be nonnegative';
ELSEIF p_amount = 0 THEN
SET p_category = 'ZERO';
ELSE
SET p_category = 'POSITIVE';
END IF;
IF uses THEN, optional ELSEIF branches, optional ELSE and END IF. Conditions follow SQL three-valued logic; UNKNOWN does not act as TRUE.
This procedural IF statement differs from MySQL's scalar IF() function.
13. CASE Statements
The simple CASE compares one expression:
CASE p_status
WHEN 'N' THEN SET p_label = 'NEW';
WHEN 'P' THEN SET p_label = 'PAID';
ELSE SET p_label = 'OTHER';
END CASE;
The searched CASE evaluates separate predicates:
CASE
WHEN p_total >= 10000 THEN SET p_band = 'HIGH';
WHEN p_total >= 5000 THEN SET p_band = 'MEDIUM';
ELSE SET p_band = 'STANDARD';
END CASE;
Procedural CASE controls statements. The CASE expression used inside SELECT returns a value.
14. WHILE
WHILE checks its condition before each iteration:
SET v_counter = 1;
WHILE v_counter <= p_limit DO
INSERT INTO number_log(value) VALUES (v_counter);
SET v_counter = v_counter + 1;
END WHILE;
The body can execute zero times. Every loop needs a clear progress step and termination condition.
Set-based SQL is usually preferable for transforming sets of rows. Use procedural loops when iterations truly depend on prior procedural state.
15. REPEAT
REPEAT executes its body and checks UNTIL afterward:
SET v_counter = 1;
REPEAT
SET v_total = v_total + v_counter;
SET v_counter = v_counter + 1;
UNTIL v_counter > p_limit
END REPEAT;
The body runs at least once. The UNTIL condition ends the loop when it becomes TRUE, which is the opposite wording from a WHILE continuation condition.
Consider behavior when the condition is NULL; an UNKNOWN termination condition does not supply TRUE.
16. LOOP, LEAVE and ITERATE
LOOP has no built-in condition:
process_loop: LOOP
SET v_counter = v_counter + 1;
IF v_counter > p_limit THEN
LEAVE process_loop;
END IF;
IF MOD(v_counter, 2) = 0 THEN
ITERATE process_loop;
END IF;
INSERT INTO odd_number(value) VALUES (v_counter);
END LOOP;
LEAVE exits the named loop or block. ITERATE skips to the next iteration of the named loop. Labels make nested control flow explicit.
17. Set-Based Operations Before Loops
This loop-oriented intention:
for every expired session, delete it
is naturally expressed as:
DELETE FROM session
WHERE expires_at < CURRENT_TIMESTAMP;
The set-based statement gives the optimizer freedom to select an access plan and avoids per-row statement overhead.
Loops remain useful for cursor-driven external-style sequencing or procedural algorithms that cannot be expressed clearly as relational operations. Their use should be justified by semantics rather than familiarity with general-purpose programming.
18. Transactions in Procedures
A procedure can execute START TRANSACTION, COMMIT and ROLLBACK where permitted, but transaction ownership must be explicit.
If a reusable procedure commits internally, a caller cannot combine it atomically with additional work. A procedure intended as one complete business command may own its transaction; a lower-level composable routine often should let the caller own it.
Document whether the routine begins, commits, rolls back or assumes an existing transaction. Remember that DDL can cause implicit commits.
19. Error Handling
Stored routines can declare handlers for SQL conditions. A transactional procedure often needs an exit handler that rolls back and rethrows:
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
The handler must be declared before executable statements. Swallowing an error and returning apparent success leaves callers unable to distinguish completion from partial failure.
Detailed handlers, SIGNAL and RESIGNAL are developed with trigger and handler design, but every procedure contract should already define its failures.
20. Security Context
Stored objects execute under a SQL SECURITY context, commonly DEFINER or INVOKER as supported. A definer-security routine can expose a narrowly controlled operation without granting clients direct table privileges.
This is powerful and must be reviewed. Definer accounts, object ownership, dynamic SQL and input validation affect privilege boundaries.
Grant EXECUTE only to roles that need the routine. Avoid creating routines under an unnecessarily powerful permanent account.
21. Dynamic SQL
MySQL prepared statements can construct SQL dynamically when object names or query shape must vary. Data values should still use parameters rather than string concatenation.
Dynamic object identifiers cannot always be bound as ordinary values and need strict allow-list validation. Concatenating untrusted text creates SQL injection risk even inside a stored procedure.
Prefer static SQL when the schema objects are known. It is easier to validate, authorize, analyze and optimize.
22. Procedure Contracts
A useful procedure contract states:
- parameter names, modes, types and NULL rules;
- result sets and OUT values;
- modified tables and invariants;
- required privileges;
- transaction ownership and isolation expectations;
- errors and retryable conditions;
- idempotency behavior.
A procedure that sends external effects or creates nonrepeatable business identifiers needs an explicit retry design.
23. Deployment and Observability
Store routine definitions in version-controlled migration files. Deploy them in a known order with dependent views, grants and application releases.
Log calls at the appropriate application boundary, monitor duration and lock waits and inspect slow statements executed inside routines. A single CALL can hide several expensive statements from superficial application timing.
Test parameter boundaries, NULL inputs, missing rows, duplicate rows, concurrent calls, handler paths and privilege behavior.
24. Designing Stored Procedures
Use a procedure when centralized database behavior adds clear value. Keep it focused on one business responsibility. Prefer set-based statements, constrain parameters, use unambiguous names and handle every affected-row count.
Decide who owns the transaction before writing COMMIT. Propagate failures honestly. Limit privileges and document results.
Stored procedures work best as deliberate database interfaces. They become difficult when they mix unrelated workflows, hidden commits, broad privileges, loops over set operations and undocumented result sets.
Continue learning
Related notes
Put this topic into timed practice
Open mock tests when you want full-exam pacing, or keep drilling in practice mode.