Расширенные хранимые процедуры в MySQL
На этот раз я расскажу о чтении информации из таблиц и манипулировании ею. Ранее я рассмотрел базовое использование и использование циклов. В этой статье я рассмотрю СУЩЕСТВУЕТ, ВЫБРАТЬ В и КУРСОРЫ.
Подзапросы EXISTS просто проверяют, возвращает ли запрос какие-либо строки, и возвращает значение true, если это так. Используйте его, чтобы проверить, присутствуют ли данные в других таблицах.
IF NOT EXISTS(SELECT * FROM People) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'OMG PANIC'; END IF;
ВЫБРАТЬ В позволяет выбирать столбцы из таблицы в переменные. При выборе необходимо будет вернуть одну строку, а переменные, которые выбираются «в», должны быть уже объявлены.
SELECT id, name FROM Profile WHERE id = 10 INTO v_id, v_name;
Если ничего не найдено, вы можете объявить обработчик продолжения для SELECT INTO. Хотя я предпочитаю использовать EXISTS для проверки наличия строки и SELECT INTO, когда я знаю, что что-то уже существует, и терпеть неудачу, если этого не существует.
Для перебора результата можно использовать КУРСОР. Курсоры немного неудобны и требуют осторожности и терпения. Они следуют формату, аналогичному SELECT INTO, но должны быть объявлены заранее, открыты, извлечены и закрыты.
Им также нужны обработчики для обработки конца курсора. Они доступны только для чтения и могут перемещаться в одном направлении. Базовый КУРСОР будет выглядеть примерно так:
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE p_id INT;
DECLARE p_name CHAR(24);
DECLARE cursorForProfile CURSOR FOR SELECT id, name FROM Profile;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cursorForProfile;
read_loop: LOOP
FETCH cursorForProfile INTO p_id, p_name;
IF done THEN
LEAVE read_loop;
END IF;
#do something with p_id and p_name
END LOOP;
CLOSE cursorForProfile;
END
В этом коде много чего происходит. Во-первых, порядок объявления всего. ПЕРЕМЕННЫЕ, КУРСОРЫ, затем ОБРАБОТКИ. Такой порядок установлен. ПРОДОЛЖИТЬ ОБРАБОТЧИК запускается, когда курсор подходит к концу набора результатов. Он устанавливает для переменной done значение true, а затем переменная done используется для выхода из цикла.
Курсор становится активным с помощью OPEN, завершается с помощью CLOSE и считывается с помощью FETCH INTO x, y.
В качестве рабочего примера я напишу надуманную процедуру, которая обновляет количество дней доступности для отеля. В отелях есть номера, а в номерах есть вместимость. В определенные дни доступно для продажи определенное количество комнат. Эта процедура добавит доступность для всех номеров в отеле на один день.
Схема (доступна по сути) выглядит примерно так:
Sproc будет проверять, есть ли в наличии комнаты, если есть комнаты, он считывает их с курсора и вставляет в таблицу наличия на заданную дату.
Базовый sproc выглядит примерно так:
CREATE PROCEDURE `addAvailability`(
IN p_idHotel INT,
IN p_date DATE
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING
BEGIN
ROLLBACK;
RESIGNAL;
END;
IF NOT EXISTS(SELECT * FROM Room WHERE idHotel = p_idHotel) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'No Rooms';
END IF;
END
Затем добавляем курсор, и цикл выглядит так:
CREATE PROCEDURE `addAvailability`(
IN p_idHotel INT,
IN p_date DATE
)
BEGIN
#variables
DECLARE v_done INT DEFAULT FALSE;
DECLARE v_idRoom INT;
DECLARE v_maxAvailable INT;
DECLARE v_price DECIMAL(10, 2);
#cursors
DECLARE cursorForRoom CURSOR FOR
SELECT
idRoom,
maxAvailable,
price
FROM
Room
WHERE
idHotel = p_idHotel;
#handlers
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING
BEGIN
ROLLBACK;
RESIGNAL;
END;
IF NOT EXISTS(SELECT * FROM Room WHERE idHotel = p_idHotel) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'No Rooms';
END IF;
OPEN cursorForRoom;
START TRANSACTION;
read_loop: LOOP
FETCH cursorForRoom INTO v_idRoom, v_maxAvailable, v_price;
IF v_done THEN
LEAVE read_loop;
END IF;
#INSERT GOES HERE
END LOOP;
COMMIT;
CLOSE cursorForRoom;
END
Наконец, добавление вставки в sproc выглядит так:
CREATE PROCEDURE `addAvailability`(
IN p_idHotel INT,
IN p_date DATE
)
BEGIN
#variables
DECLARE v_done INT DEFAULT FALSE;
DECLARE v_idRoom INT;
DECLARE v_maxAvailable INT;
DECLARE v_price DECIMAL(10, 2);
#cursors
DECLARE cursorForRoom CURSOR FOR
SELECT
idRoom,
maxAvailable,
price
FROM
Room
WHERE
idHotel = p_idHotel;
#handlers
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING
BEGIN
ROLLBACK;
RESIGNAL;
END;
IF NOT EXISTS(SELECT * FROM Room WHERE idHotel = p_idHotel) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'No Rooms';
END IF;
OPEN cursorForRoom;
START TRANSACTION;
read_loop: LOOP
FETCH cursorForRoom INTO v_idRoom, v_maxAvailable, v_price;
IF v_done THEN
LEAVE read_loop;
END IF;
INSERT INTO
Availability
SET
idAvailability = NULL,
idRoom = v_idRoom,
numberAvailable = v_maxAvailable,
price = v_price,
date = p_date;
END LOOP;
COMMIT;
CLOSE cursorForRoom;
END
Практика выглядит так:
mysql> CALL addAvailability(1, '2017-12-10'); ERROR 1644 (45000): No Rooms mysql> INSERT INTO Hotel SET idHotel = 1, name = 'Peter\'s Pad'; Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO Room SET idRoom = NULL, idHotel = 1, beds = 1, capacity = 2, price = 10.00, maxAvailable = 10; Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO Room SET idRoom = NULL, idHotel = 1, beds = 1, capacity = 1, price = 5.00, maxAvailable = 17; Query OK, 1 row affected (0.00 sec) mysql> CALL addAvailability(1, '2017-12-10'); Query OK, 0 rows affected (0.00 sec) mysql> SELECT * FROM Availability; +----------------+--------+-----------------+-------+------------+ | idAvailability | idRoom | numberAvailable | price | date | +----------------+--------+-----------------+-------+------------+ | 1 | 1 | 10 | 10.00 | 2017-12-10 | | 2 | 2 | 17 | 5.00 | 2017-12-10 | +----------------+--------+-----------------+-------+------------+ 2 rows in set (0.00 sec) mysql> CALL addAvailability(1, '2017-12-10'); ERROR 1062 (23000): Duplicate entry '1-2017-12-10' for key 'UNIQUE'
Это покрывает продвинутые sprocs в MySQL.