Не всегда удобно умещать необходимые действия над СУБД в сложные sql запросы. Иногда в одном запросе действия могут конфликтовать друг с другом, да и по синтаксису легче разбить логику на более простые запросы. Тут могут помочь хранимые процедуры mysql, а также функции. Рассмотрим примеры хранимых процедур.
Есть две таблицы — ответов и голосов за них. Необходимо удалить отдельные голоса у ответов по определенным условиям. Ситуация выполнить это одним запросом осложнена во-первых тем, что поля в таблице ответов обновляются триггерами, при удалении записей из таблицы голосов. Во-вторых в таблице голосов десятки миллионов записей. Напишем такую хранимую процедуру:
DELIMITER $$ CREATE DEFINER=`root`@`localhost` PROCEDURE `dorepeat_delete_votes`(IN `p1` INT) NO SQL BEGIN SET @x = 0; SET @id = 0; SET @c = 0; REPEAT SELECT app_vote.id FROM app_vote WHERE app_vote.answer_id = (SELECT answers.id FROM answers WHERE answers.id = app_vote.answer_id AND answers.quality >= 2) AND app_vote.useless = 1 LIMIT 1 INTO @id; SET @id = IFNULL(@id, 0); DELETE FROM app_vote WHERE id = @id; SET @c = (SELECT ROW_COUNT() + @c); SET @x = IF(@id > 0, @x + 1, p1); UNTIL @x >= p1 END REPEAT; SELECT @c AS deleted; END$$ DELIMITER ;
Здесь мы используем пользовательские переменные и оператор REPEAT — это аналог конструкции do…while в других языках. Процедура принимает входящий параметр — максимальное число итераций цикла. Также итерации будут остановлены по условию отсутствия удаленных записей. Таким образом при вызове процедуры мы можем контролировать процесс.
CALL `dorepeat_delete_votes`(1000);
Простейший пример процедуры с циклом из документации:
mysql> delimiter // mysql> CREATE PROCEDURE dorepeat(p1 INT) BEGIN SET @x = 0; REPEAT SET @x = @x + 1; UNTIL @x > p1 END REPEAT; END // Query OK, 0 rows affected (0.00 sec) mysql> CALL dorepeat(1000)// Query OK, 0 rows affected (0.00 sec) mysql> SELECT @x// +------+ | @x | +------+ | 1001 | +------+ 1 row in set (0.00 sec)
Другой вариант аналогичной по функционалу процедуры:
DELIMITER $$ CREATE DEFINER=`root`@`localhost` PROCEDURE `del_votes`() NO SQL BEGIN DECLARE i INTEGER DEFAULT 0; DECLARE n INTEGER DEFAULT 0; SELECT app_vote.id FROM app_vote WHERE app_vote.answer_id = (SELECT answers.id FROM answers WHERE answers.id = app_vote.answer_id AND answers.quality >= 2) AND app_vote.useless = 1 LIMIT 1 INTO i; WHILE i > 0 DO DELETE FROM app_vote WHERE id = i; SELECT app_vote.id FROM app_vote WHERE app_vote.answer_id = (SELECT answers.id FROM answers WHERE answers.id = app_vote.answer_id AND answers.quality >= 2) AND app_vote.useless = 1 LIMIT 1 INTO i; SET n = n + 1; END WHILE; SELECT n; END$$ DELIMITER ;
Здесь мы используем локальные переменные и удаляем, но без контроля максимального числа итераций.