MENU
Compound Statements
A compound statement, such as BEGIN...END, is a block of SQL statements treated as a single unit. Compound statements are the building blocks for stored PROCEDUREs, stored FUNCTIONs, EVENTs, and TRIGGERs.PROCEDURE, FUNCTION
|
CREATE [DEFINER = user] PROCEDURE [IF NOT EXISTS] sp_name ([proc_parameter[,...]]) [characteristic ...] routine_body CREATE [DEFINER = user] FUNCTION [IF NOT EXISTS] sp_name ([func_parameter[,...]]) RETURNS type [characteristic ...] routine_body proc_parameter: [IN | OUT | INOUT] param_name type func_parameter: param_name type type: any valid MySQL data type characteristic: COMMENT 'string' | LANGUAGE SQL | [NOT] DETERMINISTIC | {CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA} | SQL SECURITY {DEFINER | INVOKER} ALTER {PROCEDURE|FUNCTION} routine [characteristic ...] DROP {PROCEDURE|FUNCTION} [IF EXISTS] routine |
routine_body is any valid SQL routine statement, typically a BEGIN...END compound statement. A procedure is invoked with the CALL keyword.
An IN parameter passes a value into a procedure; any modification to it inside the procedure is not visible outside. An OUT parameter passes a value from the procedure out to the caller's variable. An INOUT parameter passes a value in, and any modification to it is visible outside the procedure once the call returns. Function parameters are always regarded as IN parameters, and a stored function cannot return a result set.
LANGUAGE SQL is currently ignored, since only SQL routines are supported. A routine is DETERMINISTIC if it always produces the same result for the same input parameters – the optimizer can execute such routines faster. The default is NOT DETERMINISTIC; a routine containing NOW() or RAND() is nondeterministic. CONTAINS SQL, the default, indicates the routine contains no statements that read or write data. NO SQL indicates the routine contains no SQL statements at all. READS SQL DATA indicates the routine contains statements that read data (SELECT) but do not write it. MODIFIES SQL DATA indicates the routine contains statements that write data (INSERT, DELETE, UPDATE). SQL SECURITY specifies whether the routine executes with the privileges of the account named in the DEFINER clause, or with the privileges of the invoking user. As of MySQL 8.4, the SET_ANY_DEFINER and ALLOW_NONEXISTENT_DEFINER dynamic privileges (introduced in 8.2, and now the sole mechanism for this, since the broader SET_USER_ID privilege they replaced was removed in 8.4) give finer-grained control over which accounts may set an arbitrary or nonexistent account as the DEFINER of a stored procedure, function, event, or trigger, instead of requiring the SUPER privilege. See Privileges.
The following are not permitted inside stored routines: LOCK TABLES/UNLOCK TABLES, ALTER VIEW, LOAD DATA/LOAD TABLE, and any statement not permitted in SQL prepared statements (except SIGNAL, RESIGNAL, and GET DIAGNOSTICS). To begin a transaction inside a stored program, use START TRANSACTION. Stored functions additionally disallow commit/rollback statements, statements that return a result set, FLUSH statements, recursive function calls, and statements that modify a table already being used by the statement that invoked the function.
| Stored Procedure vs. Function | ||
| Returns | Zero or more values, via OUT/INOUT parameters or a result set | A single scalar value |
| Can use transactions? | Yes | No |
| Can output to parameters? | Yes (OUT, INOUT) | No – parameters are always IN |
| Can call the other kind? | Can call a function | Cannot call a procedure |
| Usable in SELECT, WHERE, HAVING? | No – invoked with CALL | Yes |
| Supports exception handling (DECLARE...HANDLER)? | Yes | Yes |
The worked example below invokes a procedure that fills a table with 0-8 random integers in a given range, and a function that returns the average of the integers stored in that table. Notice the use of DELIMITER to temporarily change the statement delimiter so that semicolons inside the routine body do not terminate the CREATE statement early.
JavaScript Stored Programs
MySQL 9.0 adds a second stored-routine language alongside SQL: CREATE FUNCTION/PROCEDURE ... LANGUAGE JAVASCRIPT writes a routine body in JavaScript instead of SQL, executed inside the server by the MySQL Multilingual Engine (MLE) component. This is a MySQL Enterprise Edition feature only – it is not available in MySQL Community Edition. See the worked example below for the basic shape of a JavaScript function.MySQL 9.2 extends JavaScript stored programs further, still Enterprise-only: JavaScript code can call SQL user-defined functions, stored procedures, and variables; a JS transaction API (START TRANSACTION, COMMIT, ROLLBACK, and SET AUTOCOMMIT, callable from JavaScript) lets a routine manage transactions directly; and CREATE LIBRARY, DROP LIBRARY, and SHOW CREATE LIBRARY let a block of reusable JavaScript be defined once as a library module and imported into multiple JS routines, instead of duplicating the same code in each one.
DECLARE, SET, RESET
DECLARE must appear inside, and at the beginning of, a BEGIN...END block. Variable and condition declarations must appear before cursor or handler declarations.| DECLARE var_name [, var_name] ... type [DEFAULT value] |
Declares one or more local variables, optionally with a default value.
DROP PROCEDURE IF EXISTS proc;
DELIMITER //
CREATE PROCEDURE proc()
BEGIN
DECLARE a,b,c INT DEFAULT 2;
SELECT a,b,c;
END //
DELIMITER ;
CALL proc();| a | b | c |
|---|---|---|
| 2 | 2 | 2 |
|
SET variable = expr [, variable = expr] ... variable: @user_var_name | param_name | local_var_name | [SESSION | @@SESSION. | @@] system_var_name | {GLOBAL | @@GLOBAL.} system_var_name | {PERSIST | @@PERSIST.} system_var_name | {PERSIST_ONLY | @@PERSIST_ONLY.} system_var_name |
Assigns a value to a user-defined or system variable.
A user-defined global variable begins with the symbol @. A function/procedure parameter or local variable is never prefixed with @. Any change to a SESSION system variable lasts until the session closes; a change to a GLOBAL variable lasts until the server restarts. SET PERSIST sets a global system variable's value and also persists it across server restarts (not every global system variable can be persisted). Some system variables exist as both SESSION and GLOBAL. To view current values, use SHOW [GLOBAL|SESSION] VARIABLES [LIKE 'pattern' | WHERE expr].
The key difference between the two: SET assigns a value to a user-defined or system variable within any SQL statement and is accessible from any statement within the session; DECLARE introduces a local variable inside a stored procedure, function, event, or trigger, accessible only within the body where it is declared.
CREATE PROCEDURE p(increment INT)
BEGIN
DECLARE counter INT DEFAULT 0;
WHILE counter < 10 DO
-- ... do work ...
SET counter = counter + increment;
END WHILE;
END;|
SET GLOBAL max_connections = 1000; SET @@GLOBAL.max_connections = 1000; SET SESSION sql_mode = 'TRADITIONAL'; SET LOCAL sql_mode = 'TRADITIONAL'; SET @@SESSION.sql_mode = 'TRADITIONAL'; SET @@LOCAL.sql_mode = 'TRADITIONAL'; SET @@sql_mode = 'TRADITIONAL'; SET sql_mode = 'TRADITIONAL'; SET PERSIST max_connections = 1000; SET @@PERSIST.max_connections = 1000; SET @@SESSION.max_join_size = DEFAULT; SET @@SESSION.max_join_size = @@GLOBAL.max_join_size; SET @x = 1, SESSION sql_mode = ''; SET GLOBAL sort_buffer_size = 1000000, SESSION sort_buffer_size = 1000000; SET @@GLOBAL.sort_buffer_size = 1000000, @@LOCAL.sort_buffer_size = 1000000; SET GLOBAL max_connections = 1000, sort_buffer_size = 1000000; SET @@GLOBAL.sort_buffer_size = 50000, sort_buffer_size = 1000000; |
Equivalent and combined forms of SET for user variables and system variables at various scopes.
SET @a = 900 + 99;
SELECT @a,
@@system_time_zone,
@@global.autocommit;| RESET PERSIST [[IF EXISTS] system_var_name] |
Removes persisted global system variable settings from the mysqld-auto.cnf option file in the data directory. A removed variable is no longer initialized from mysqld-auto.cnf at server startup. With no variable named, all persisted settings are removed.
-- a quick way to inspect a numbered listing of rows in MySQL Workbench,
-- incrementing a user-defined variable per row
SET @a=0;
SELECT @a:=@a+1 FROM INFORMATION_SCHEMA.TABLES;LOOP, REPEAT, WHILE, CASE, IF
Labels allow a set of statements to be executed repeatedly.|
[begin_label:] BEGIN [statement_list] END [end_label] [begin_label:] LOOP statement_list END LOOP [end_label] [begin_label:] REPEAT statement_list UNTIL search_condition END REPEAT [end_label] [begin_label:] WHILE search_condition DO statement_list END WHILE [end_label] |
begin_label can be given without end_label. If end_label is present it must match begin_label; end_label cannot be given without begin_label.
Inside a labeled construct, ITERATE jumps execution back to the beginning of the construct, and LEAVE exits the block of statements. The worked example below sums increments to 100 using a LOOP with ITERATE and LEAVE. RETURN terminates a stored function and returns its value.
Unlike a CASE expression, a CASE statement cannot have an ELSE NULL clause, and it is terminated with END CASE instead of END. IF...THEN...ELSE...END IF provides ordinary conditional branching. See the worked example for both constructs.
CURSOR
A cursor is a result set scoped to a stored routine. It cannot be updated, and it can only be traversed forward, one row at a time, without skipping rows. Cursor declarations must appear after variable and condition declarations, and before handler declarations.| DECLARE cursor_name CURSOR FOR select_statement; |
Declares a cursor over the result set of select_statement. Use OPEN to start reading, FETCH ... INTO var_list to read the next row, and CLOSE to release it. A CONTINUE HANDLER FOR NOT FOUND is the idiomatic way to detect that a cursor has been exhausted, since a cursor does not raise an exception at end-of-data by itself.
CONDITION, HANDLER
A HANDLER specifies what to do when a CONDITION occurs.|
DECLARE condition_name CONDITION FOR condition_value condition_value: mysql_error_code | SQLSTATE [VALUE] sqlstate_value |
Gives a name to a MySQL error code or SQLSTATE value so it can be referred to later in a handler declaration.
|
DECLARE handler_action HANDLER FOR condition_value [, condition_value] ... statement handler_action: CONTINUE | EXIT | UNDO condition_value: mysql_error_code | SQLSTATE [VALUE] sqlstate_value | condition_name | SQLWARNING | NOT FOUND | SQLEXCEPTION |
| CONTINUE lets execution continue after the handler runs. EXIT terminates the enclosing BEGIN...END compound statement. UNDO is not supported. NOT FOUND controls what happens when a cursor reaches the end of its data set. Use SHOW WARNINGS or SHOW ERRORS to inspect conditions or errors directly. |
SQL statements produce diagnostic information; GET DIAGNOSTICS retrieves either statement or condition information.
|
GET [CURRENT | STACKED] DIAGNOSTICS { statement_information_item [, statement_information_item] ... | CONDITION condition_number condition_information_item [, condition_information_item] ... } statement_information_item_name: NUMBER | ROW_COUNT condition_information_item_name: CLASS_ORIGIN | SUBCLASS_ORIGIN | RETURNED_SQLSTATE | MESSAGE_TEXT | MYSQL_ERRNO | CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_NAME | CATALOG_NAME | SCHEMA_NAME | TABLE_NAME | COLUMN_NAME | CURSOR_NAME |
| CURRENT, the default, retrieves information from the current diagnostic area. STACKED retrieves information from the second diagnostic area, available only while inside a condition handler. |
RESIGNAL passes on the error condition information available while a condition handler is executing, so the handler can both react to the error and still surface it – without RESIGNAL, executing another SQL statement inside the handler destroys the information that triggered it. RESIGNAL may also modify the information before passing it on. A bare RESIGNAL means "pass on the error unchanged"; RESIGNAL with a condition value means "push a (possibly modified) condition into the current diagnostics area".
|
RESIGNAL [condition_value] [SET signal_information_item [, signal_information_item] ...] SIGNAL condition_value [SET signal_information_item [, signal_information_item] ...] condition_value: SQLSTATE [VALUE] sqlstate_value | condition_name signal_information_item: condition_information_item_name = simple_value_specification |
SIGNAL raises an error or warning, handing information to a handler, an outer part of the application, or the client. condition_information_item_name accepts the same names as GET DIAGNOSTICS above (except NUMBER/ROW_COUNT).
EVENT
The event scheduler thread must be turned on for scheduled events to run; setting @@global.event_scheduler to DISABLED (rather than OFF) prevents the state from being changed again at runtime.|
CREATE [DEFINER = {user | CURRENT_USER}] EVENT [IF NOT EXISTS] event_name ON SCHEDULE schedule [ON COMPLETION [NOT] PRESERVE] [ENABLE | DISABLE | DISABLE ON SLAVE] [COMMENT 'comment'] DO event_body; schedule: AT timestamp [+ INTERVAL interval] ... | EVERY interval [STARTS timestamp [+ INTERVAL interval] ...] [ENDS timestamp [+ INTERVAL interval] ...] interval: quantity {YEAR|QUARTER|MONTH|DAY|HOUR|MINUTE|WEEK|SECOND|YEAR_MONTH| DAY_HOUR|DAY_MINUTE|DAY_SECOND|HOUR_MINUTE|HOUR_SECOND|MINUTE_SECOND} ALTER [DEFINER = {user | CURRENT_USER}] EVENT event_name [ON SCHEDULE schedule] [ON COMPLETION [NOT] PRESERVE] [RENAME TO new_event_name] [ENABLE | DISABLE | DISABLE ON SLAVE] [COMMENT 'comment'] [DO event_body] DROP EVENT [IF EXISTS] event_name |
| DEFINER records the user who defines the event. ON COMPLETION PRESERVE keeps the event definition after it expires instead of dropping it (ON COMPLETION NOT PRESERVE is the default). DISABLE stops the event from running (ENABLE is the default); DISABLE ON SLAVE stops it from running on a replica. |
TRIGGER
|
CREATE [DEFINER = user] TRIGGER [IF NOT EXISTS] trigger_name trigger_time trigger_event ON tbl_name FOR EACH ROW [trigger_order] trigger_body trigger_time: {BEFORE | AFTER} trigger_event: {INSERT | UPDATE | DELETE} trigger_order: {FOLLOWS | PRECEDES} other_trigger_name DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name |
| INSERT activates the trigger via INSERT, LOAD DATA, and REPLACE statements. UPDATE activates it via UPDATE statements. DELETE activates it via DELETE and REPLACE statements. Cascaded foreign key actions (see Indexes) do not activate triggers. A table cannot have two triggers with the same trigger name and the same action time (e.g. two BEFORE INSERT triggers). |
Column values are accessed inside a trigger body with OLD.colName or NEW.colName. Multiple triggers can share the same trigger event and action time; by default they activate in creation order. To control the order explicitly, add FOLLOWS or PRECEDES other_trigger_name after FOR EACH ROW – FOLLOWS activates the new trigger after the named one, PRECEDES activates it before. See DECLARE, SET, RESET above for the use of local and user-defined variables inside triggers.
DROP PROCEDURE IF EXISTS initialize_tbl;
DROP FUNCTION IF EXISTS tAverage;
DELIMITER //
CREATE PROCEDURE initialize_tbl
(IN pmin INT, IN pmax INT, OUT cnt INT)
NOT DETERMINISTIC
MODIFIES SQL DATA
BEGIN
DROP TABLE IF EXISTS tbl;
CREATE TABLE tbl(num INT, INDEX (num));
INSERT INTO tbl VALUES
(rand()*100),(rand()*100),(rand()*100),
(rand()*100),(rand()*100),(rand()*100),
(rand()*100),(rand()*100);
DELETE FROM tbl WHERE num<pmin OR num>pmax;
SET cnt = (SELECT count(num) FROM tbl);
END //
CREATE FUNCTION tAverage()
RETURNS INT
DETERMINISTIC
READS SQL DATA
BEGIN
RETURN (SELECT AVG(num) FROM tbl);
END //
DELIMITER ;
CALL initialize_tbl(30,70,@C);
SELECT @C, tAverage(), num FROM tbl;| @C | tAverage() | num |
|---|---|---|
| 3 | 47 | 30 |
| 3 | 47 | 53 |
| 3 | 47 | 59 |
-- MySQL 9.0+, requires the Multilingual Engine (MLE) component and
-- MySQL Enterprise Edition -- not available on Community Edition servers.
CREATE FUNCTION gcd(a INT, b INT)
RETURNS INT
NO SQL
LANGUAGE JAVASCRIPT
AS $$
let x = Math.abs(a);
let y = Math.abs(b);
while (y) {
const t = y;
y = x % y;
x = t;
}
return x;
$$;
SELECT gcd(75, 220), gcd(75, 225);
-- 9.2+: CREATE LIBRARY packages reusable JavaScript for import into
-- multiple JS routines via a USING clause (also Enterprise-only,
-- illustrative only):
-- CREATE LIBRARY mathutils LANGUAGE JAVASCRIPT AS $$
-- export function square(n) { return n * n; }
-- $$;
-- CREATE FUNCTION squared(n INT) RETURNS INT
-- LANGUAGE JAVASCRIPT
-- USING (mathutils AS mathutils)
-- AS $$
-- return mathutils.square(n);
-- $$;-- LOOP with ITERATE / LEAVE
DROP PROCEDURE IF EXISTS sum100;
DELIMITER //
CREATE PROCEDURE sum100(INOUT p INT)
BEGIN
lbl: LOOP
SET p = p + 1;
IF p < 100 THEN ITERATE lbl; END IF;
LEAVE lbl;
END LOOP lbl;
END //
DELIMITER ;
SET @s=0;
CALL sum100(@s);
SELECT @s;
-- 100
-- CASE statement (no ELSE NULL, terminated with END CASE)
DROP FUNCTION IF EXISTS func;
DELIMITER //
CREATE FUNCTION func()
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE v INT DEFAULT 1;
CASE v
WHEN 2 THEN RETURN v*v;
WHEN 3 THEN RETURN v*v*v;
ELSE
BEGIN
RETURN v;
END;
END CASE;
END; //
DELIMITER ;
SELECT func();
-- 1
-- IF...THEN...ELSE...END IF, printing prime numbers up to 1000
DELIMITER //
CREATE PROCEDURE PRIMEFUNC(OUT foutput VARCHAR(1200))
BEGIN
DECLARE num INT;
DECLARE counter INT;
DECLARE isprime INT;
SET num = 3;
SET foutput = "2";
outerlabel : LOOP
SET counter = 2;
SET isprime = 1;
innerlabel : LOOP
IF counter <= sqrt(num) THEN
IF num mod counter = 0 THEN
SET isprime = 0;
LEAVE innerlabel;
END IF;
SET counter = counter + 1;
ITERATE innerlabel;
ELSE
LEAVE innerlabel;
END IF;
END LOOP innerlabel;
IF isprime = 1 THEN
SET foutput = CONCAT(foutput,"&",num);
END IF;
SET num = num + 1;
IF num <= 1000 THEN ITERATE outerlabel;
ELSE LEAVE outerlabel;
END IF;
END LOOP outerlabel;
END //
DELIMITER ;
CALL PRIMEFUNC(@fop);
SELECT @fop;
-- '2&3&5&7&11&13&17&19&23&29&31&37&41&43&47&53&59&61&67&71...'DROP TABLE IF EXISTS test_t1, test_t2, test_t3;
CREATE TABLE test_t1 (id INT, data INT);
CREATE TABLE test_t2 (i INT);
CREATE TABLE test_t3 (a CHAR(20), b INT);
INSERT INTO test_t1 VALUES (1,50),(2,80);
INSERT INTO test_t2 VALUES (60),(70);
DROP PROCEDURE IF EXISTS curDemo;
DELIMITER //
CREATE PROCEDURE curDemo()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE a CHAR(20) DEFAULT 'row';
DECLARE x, y, c INT;
DECLARE cur1 CURSOR FOR SELECT id,data FROM test_t1;
DECLARE cur2 CURSOR FOR SELECT i FROM test_t2;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET done = TRUE;
OPEN cur1;
OPEN cur2;
read_loop: LOOP
FETCH cur1 INTO x, y;
FETCH cur2 INTO c;
IF done THEN
LEAVE read_loop;
END IF;
IF x < y THEN
INSERT INTO test_t3 VALUES (a,x);
ELSE
INSERT INTO test_t3 VALUES (a,y);
END IF;
END LOOP;
CLOSE cur1;
CLOSE cur2;
END //
DELIMITER ;
CALL curDemo();
SELECT * FROM test_t3;| a | b |
|---|---|
| row | 1 |
| row | 2 |
-- DECLARE ... CONDITION, GET DIAGNOSTICS
DROP PROCEDURE IF EXISTS handled_call;
DELIMITER //
CREATE PROCEDURE handled_call()
BEGIN
DECLARE no_such_table CONDITION FOR 1051;
DECLARE CONTINUE HANDLER FOR no_such_table
BEGIN
-- body of handler
SELECT 'caught missing table' AS note;
END;
DROP TABLE IF EXISTS a_table_that_does_not_exist;
END //
DELIMITER ;
CALL handled_call();
GET CURRENT DIAGNOSTICS @a=NUMBER, @b=ROW_COUNT;
SELECT @a, @b;-- RESIGNAL: react to an error and still surface it
DROP TABLE IF EXISTS xx;
DELIMITER //
CREATE PROCEDURE p ()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SET @error_count = @error_count + 1;
IF @a = 0 THEN RESIGNAL SQLSTATE '45000' SET MYSQL_ERRNO=5; END IF;
END;
DROP TABLE xx;
END //
DELIMITER ;
SET @error_count = 0;
SET @a = 0;
SET @@max_error_count = 2;
CALL p();
SHOW ERRORS;| Level | Code | Message |
|---|---|---|
| Error | 1051 | Unknown table 'xx' |
| Error | 5 | Unknown table 'xx' |
-- SIGNAL: raise a condition explicitly
DROP PROCEDURE IF EXISTS raise_demo;
DELIMITER //
CREATE PROCEDURE raise_demo(pval INT)
BEGIN
DECLARE specialty CONDITION FOR SQLSTATE '45000';
IF pval = 0 THEN
SIGNAL SQLSTATE '01000';
ELSEIF pval = 1 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'An error occurred';
ELSEIF pval = 2 THEN
SIGNAL specialty
SET MESSAGE_TEXT = 'An error occurred';
ELSE
SIGNAL SQLSTATE '01000'
SET MESSAGE_TEXT = 'A warning occurred', MYSQL_ERRNO = 1000;
END IF;
END //
DELIMITER ;SET @@global.event_scheduler=ON;
-- runs once, one and a half hours from now
DROP TABLE IF EXISTS event_tbl;
CREATE TABLE event_tbl (a INT);
DROP EVENT IF EXISTS myEvent;
CREATE
DEFINER = 'root'@'localhost'
EVENT myEvent
ON SCHEDULE AT CURRENT_TIMESTAMP
+ INTERVAL 1 HOUR
+ INTERVAL 30 MINUTE
DO
INSERT INTO event_tbl VALUES (1);
-- runs once every hour, within a bounded period
DROP EVENT IF EXISTS myHourlyEvent;
CREATE EVENT myHourlyEvent
ON SCHEDULE EVERY 1 HOUR
STARTS '2026-08-16 11:00:00'
ENDS '2026-08-16 19:00:00'
DO
INSERT INTO event_tbl VALUES (1);SET @sum=0;
DROP TABLE IF EXISTS account;
DROP TRIGGER IF EXISTS ins_sum;
DROP TRIGGER IF EXISTS ins_transaction;
CREATE TABLE account (amount DECIMAL(10,2));
CREATE TRIGGER ins_sum
BEFORE INSERT ON account
FOR EACH ROW SET @sum = @sum + NEW.amount;
INSERT INTO account VALUES (300),(200);
SELECT @sum;| @sum |
|---|
| 500.00 |
-- a second BEFORE INSERT trigger on the same table, ordered
-- to run before ins_sum via PRECEDES
SET @deposits=0, @withdrawals=0;
CREATE TRIGGER ins_transaction BEFORE INSERT ON account
FOR EACH ROW PRECEDES ins_sum
SET
@deposits = @deposits + IF(NEW.amount>0,NEW.amount,0),
@withdrawals = @withdrawals + IF(NEW.amount<0,-NEW.amount,0);
INSERT INTO account VALUES (150),(-50);
SELECT @sum, @deposits, @withdrawals;| @sum | @deposits | @withdrawals |
|---|---|---|
| 600.00 | 150.00 | 50.00 |