AD JB Toolbox 01 EN

MariaDB Tutorial / Use Stored Procedures, Functions, and Triggers

A stored procedure is an explicit server-side operation invoked with CALL, a stored function returns a value inside a SQL expression, and a trigger runs automatically in response to a table change. Their invocation, privileges, and transaction responsibilities differ even though all three store SQL logic on the server.

Choose an object by its role

Object Invocation Typical role
Stored procedure CALL name(arguments) An explicit operation consisting of multiple SQL statements
Stored function name(arguments) inside a SQL expression A reusable rule returning one computed value
Trigger Automatically on INSERT, UPDATE, or DELETE A short row-level action coupled to a table change

Procedures are not required to modify data, and stored functions are not universally limited to arithmetic. This lab makes the differences concrete: a procedure changes stock, a function calculates an amount, and a trigger records old and new stock. MariaDB triggers run for each row, so a statement changing many rows also incurs the corresponding trigger work. Compare caller-controlled commit/rollback in Start InnoDB Transactions with COMMIT and ROLLBACK and the stored-view execution context in Save Reusable Query Definitions with VIEW to distinguish these objects.

Hidden server-side behavior can complicate troubleshooting. Distinguish an operation the application calls deliberately from logic triggered by every matching table write. Decide whether a rule belongs in application validation or must be enforced across several write paths before moving it into the database.

Prepare privileges and the two InnoDB tables

Use a dedicated appops_lab schema and a lab account with CREATE ROUTINE, TRIGGER, and the required table privileges. Calling routines also requires EXECUTE. Review accounts and grants rather than granting global administrator rights to an application. Binary-log policies can add requirements for creating functions; have the DBA check that policy instead of weakening server-wide trust settings merely to make the lab succeed.

CREATE DATABASE IF NOT EXISTS appops_lab
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE appops_lab;
CREATE TABLE routine_stock (
  product_id INT NOT NULL PRIMARY KEY,
  stock INT NOT NULL CHECK (stock >= 0)
) ENGINE=InnoDB;
CREATE TABLE routine_audit (
  audit_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  product_id INT NOT NULL,
  old_stock INT NOT NULL,
  new_stock INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO routine_stock VALUES (1, 8);

Both tables use the transactional InnoDB engine. That condition allows the stock update and the trigger’s audit insertion to roll back together. If an exercise object already exists, inspect its definition and data before repeating creation; the setup intentionally does not drop existing objects.

Create all three objects with complete definitions

The following script uses the mariadb command-line client’s syntax. DELIMITER configures how that client recognizes the end of a statement; it is not a server SQL statement. Use the script-execution feature of a graphical editor. With a Python driver, send each complete CREATE statement once without DELIMITER lines or the // terminators.

DELIMITER //
CREATE FUNCTION line_total(p_price DECIMAL(10,2), p_qty INT)
RETURNS DECIMAL(12,2)
DETERMINISTIC NO SQL SQL SECURITY INVOKER
BEGIN
  IF p_price IS NULL OR p_qty IS NULL OR p_price < 0 OR p_qty < 0 THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Nonnegative price and quantity required';
  END IF;
  RETURN ROUND(p_price * p_qty, 2);
END//

CREATE PROCEDURE adjust_stock(IN p_product_id INT, IN p_delta INT)
SQL SECURITY INVOKER
MODIFIES SQL DATA
BEGIN
  IF p_delta IS NULL OR p_delta = 0 THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Nonzero stock delta required';
  END IF;
  UPDATE routine_stock SET stock = stock + p_delta
  WHERE product_id = p_product_id AND stock + p_delta >= 0;
  IF ROW_COUNT() <> 1 THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Missing product or insufficient stock';
  END IF;
END//

CREATE TRIGGER audit_stock_update
AFTER UPDATE ON routine_stock FOR EACH ROW
BEGIN
  IF OLD.stock <> NEW.stock THEN
    INSERT INTO routine_audit (product_id, old_stock, new_stock)
    VALUES (NEW.product_id, OLD.stock, NEW.stock);
  END IF;
END//
DELIMITER ;

line_total rejects negative or NULL inputs and returns a two-decimal amount. Reassess its DECIMAL range for large quantities or prices. The declarations DETERMINISTIC NO SQL describe this particular implementation and must remain truthful if the implementation changes. The CREATE FUNCTION reference explains the characteristics and security context.

adjust_stock updates exactly one existing product only when the resulting stock is nonnegative. Otherwise it raises a business error with SIGNAL. Read ROW_COUNT() immediately after the UPDATE; inserting another statement can change which operation it describes. The CREATE PROCEDURE reference covers parameters and invocation security.

The trigger inserts an audit row only when stock actually changes. This is a change-recording exercise, not an independent tamper-resistant audit system. Refer to CREATE TRIGGER for OLD and NEW row values and trigger timing.

Compare both tables across rollback and commit

SELECT line_total(12.50, 3) AS amount;
START TRANSACTION;
CALL adjust_stock(1, -3);
SELECT product_id, stock FROM routine_stock;
SELECT product_id, old_stock, new_stock FROM routine_audit;
ROLLBACK;
SELECT stock FROM routine_stock WHERE product_id = 1;
SELECT COUNT(*) AS audit_rows FROM routine_audit;

The amount is 37.50. Inside the transaction, stock becomes five and the audit row records eight becoming five. After ROLLBACK, stock must be eight again and the audit row count must be zero. An auto-increment identifier need not be reused after a rollback; gaps in audit IDs are not evidence of a failed rollback.

START TRANSACTION;
CALL adjust_stock(1, -2);
COMMIT;
SELECT stock FROM routine_stock WHERE product_id = 1;
SELECT old_stock, new_stock FROM routine_audit ORDER BY audit_id;

After this commit, stock is six and one audit row records eight becoming six. The procedure deliberately has no internal COMMIT, allowing the caller to combine it with other work. A trigger runs within the triggering statement’s transaction context and does not independently commit. Review InnoDB transactions, COMMIT, and ROLLBACK when interpreting these results.

Observe invalid inputs and privilege boundaries

Execute these statements separately. The first attempts to subtract more than the current stock of six; the second supplies a negative unit price. Both should raise SQLSTATE 45000. Verify afterward that stock remains six and the audit table still has its one committed row.

CALL adjust_stock(1, -99);
SELECT line_total(-1, 2);

The function and procedure declare SQL SECURITY INVOKER, so table access uses the caller’s privileges. An account granted EXECUTE but not UPDATE cannot modify stock through this procedure. A trigger instead uses its DEFINER’s security context; it is not declared INVOKER. Manage the trigger definer, account lifecycle, and required table access accordingly.

The trigger limitations restrict actions such as modifying the triggering table again and controlling transactions. Keep trigger work short and relevant to the database transaction rather than hiding long external operations behind a row change.

Inspect definitions and remove only disposable objects

SHOW CREATE PROCEDURE adjust_stock;
SHOW CREATE FUNCTION line_total;
SHOW TRIGGERS FROM appops_lab;

Record definitions and security contexts, and confirm that backup and restore include the required routines and triggers. A definition restored onto another server can still fail if its DEFINER account is absent or lacks privileges. When this isolated lab is finished, remove only its own objects:

DROP TRIGGER appops_lab.audit_stock_update;
DROP PROCEDURE appops_lab.adjust_stock;
DROP FUNCTION appops_lab.line_total;
DROP TABLE appops_lab.routine_audit;
DROP TABLE appops_lab.routine_stock;

An ordinary ROLLBACK does not recover dropped objects. Confirm that the names identify disposable lab objects. Changing production routines also requires coordinating callers and a deployment and recovery process.

More in This Category
MariaDB Tutorial / Create a Product Table, Insert Data, and Query It

MariaDB Tutorial / Create a Product Table, Insert Data, and Query It

Build a complete product table, insert three records, and verify its definition and query results. Practice price and stock filters, understand rerun behavior, and reset only confirmed disposable tutorial data.

MariaDB Tutorial / Avoid Duplicate Totals When Joining Multiple Tables

MariaDB Tutorial / Avoid Duplicate Totals When Joining Multiple Tables

Trace row multiplication across customer, order, item and product joins. Reconcile correct goods and shipping totals, understand DISTINCT pitfalls and preaggregate details at the order grain.

MariaDB Tutorial / Install MariaDB on Windows and Start the Server

MariaDB Tutorial / Install MariaDB on Windows and Start the Server

Set up a local MariaDB server on Windows, verify its service and TCP connection, and create a database-scoped learning account. Follow practical checks for installation, port, path, and authentication problems.

MariaDB Tutorial / Compare Isolation Levels, Locks, and Deadlocks with Two Connections

MariaDB Tutorial / Compare Isolation Levels, Locks, and Deadlocks with Two Connections

Follow explicit A/B steps to compare isolation, row-lock waits and deadlocks. Complete blocks cover resetting values, rereading, cleaning up failures and restoring each connection’s settings.

MariaDB Tutorial / Insert One Row or Multiple Rows with INSERT

MariaDB Tutorial / Insert One Row or Multiple Rows with INSERT

Practice single-row and multi-row INSERT statements, inspect stored values and defaults, and test duplicate keys, mismatched value counts and required inputs without changing existing product tables.

MariaDB Tutorial / Troubleshoot Local and Remote MariaDB Connections

MariaDB Tutorial / Troubleshoot Local and Remote MariaDB Connections

Diagnose MariaDB connections by layer, verify local TCP identity and actual table access, and review remote listener, host, firewall and certificate requirements without weakening checks by default.

MariaDB Tutorial / Import and Export CSV with Correct Paths, Types, and Encoding

MariaDB Tutorial / Import and Export CSV with Correct Paths, Types, and Encoding

Stage and validate a UTF-8 CSV before converting IDs and prices, then export through a server-side path. Review quoting, warnings, normalized duplicates, numeric bounds and field-level round-trip checks.

MariaDB Tutorial / Combine Query Results with UNION and UNION ALL

MariaDB Tutorial / Combine Query Results with UNION and UNION ALL

Stack compatible customer-query results, compare UNION with UNION ALL and observe how source labels change duplicate detection. Check column meaning, result types and ordering scope.

MariaDB Tutorial / Create Users and Grant Only Required Privileges

MariaDB Tutorial / Create Users and Grant Only Required Privileges

Create a table-scoped MariaDB reader, inspect its real account identity, test allowed reads and denied operations, then verify revocation and review account dependencies before cleanup.

MariaDB Tutorial / Build a Local MariaDB Lab with Docker Compose

MariaDB Tutorial / Build a Local MariaDB Lab with Docker Compose

Configure a local MariaDB container with Compose, file-based secrets, a healthcheck, and a named volume. Verify connections and distinguish container replacement from deleting persistent data.