Загрузка данных
-- ============================================================
-- ПРАКТИЧЕСКАЯ РАБОТА
-- Тема: «Переменные и подпрограммы в MySQL»
-- Дисциплина: МДК 11
-- Специальность: 09.02.07 Информационные системы и программирование
-- ============================================================
-- ============================================================
-- ПОДГОТОВКА: создание БД и таблицы
-- >>> СКРИНШОТ 1: выполните блок целиком, сделайте скриншот
-- вкладки Result Grid и списка таблиц слева.
-- ============================================================
CREATE DATABASE IF NOT EXISTS lab_procedures;
USE lab_procedures;
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL CHECK (price >= 0),
discount_percent DECIMAL(5, 2) DEFAULT 0.00
CHECK (discount_percent BETWEEN 0 AND 100),
quantity INT DEFAULT 0 CHECK (quantity >= 0)
) ENGINE=InnoDB;
INSERT INTO products (name, price, discount_percent, quantity) VALUES
('Ноутбук', 85000.00, 5.00, 10),
('Мышь беспроводная', 1200.00, 0.00, 50),
('Клавиатура механическая', 4500.00, 10.00, 30),
('Монитор 27"', 22000.00, 7.50, 15),
('Веб-камера', 3200.00, 0.00, 40);
SELECT * FROM products;
-- ============================================================
-- ЗАДАНИЕ 1. Пользовательские переменные
-- >>> СКРИНШОТ 2: выполните блок целиком — в результате
-- должна быть таблица с base_price, discount,
-- final_price. Сделайте скриншот.
-- ============================================================
SET @base_price = 0;
SET @discount = 0;
SET @final_price = 0;
SELECT price, discount_percent
INTO @base_price, @discount
FROM products
WHERE id = 1;
SET @final_price = @base_price * (1 - @discount / 100);
SELECT @base_price AS base_price,
@discount AS discount,
@final_price AS final_price;
-- >>> ОТВЕТ НА КОНТРОЛЬНЫЙ ВОПРОС 1:
-- DECLARE — это конструкция для локальных переменных, которая
-- работает только внутри составных операторов BEGIN...END
-- хранимых процедур, функций и триггеров. Вне программы у MySQL
-- нет контекста (блока BEGIN), в котором эта переменная могла бы
-- существовать, поэтому сервер выдаёт синтаксическую ошибку.
-- Для работы на уровне сессии используются пользовательские
-- переменные — они начинаются с @ и объявляются через SET.
-- ============================================================
-- ЗАДАНИЕ 2. Функция calc_final_price
-- >>> СКРИНШОТ 3: сначала выполните блок создания функции
-- (должно появиться сообщение об успехе в
-- разделе Schemas → Functions).
-- ============================================================
DELIMITER //
CREATE FUNCTION calc_final_price(
p_price DECIMAL(10,2),
p_discount_percent DECIMAL(5,2)
)
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
DECLARE v_discount DECIMAL(10,2);
DECLARE v_final DECIMAL(10,2);
SET v_discount = p_price * (p_discount_percent / 100.0);
SET v_final = p_price - v_discount;
RETURN v_final;
END //
DELIMITER ;
-- >>> СКРИНШОТ 3 (продолжение): теперь выполните проверочный
-- SELECT и сделайте скриншот результата.
SELECT id, name, price, discount_percent,
calc_final_price(price, discount_percent) AS final_price
FROM products;
-- >>> ОТВЕТ НА КОНТРОЛЬНЫЙ ВОПРОС 2:
-- Функция инкапсулирует формулу расчёта в одном месте: если
-- логику нужно изменить (например, добавить округление или
-- коэффициент), правка делается один раз, а не во всех запросах.
-- Это исключает ошибки копирования формулы и делает SQL-запросы
-- короче и читаемее. Функцию можно переиспользовать в SELECT,
-- WHERE, ORDER BY и внутри других процедур.
-- ============================================================
-- ЗАДАНИЕ 3. Процедура get_product_info
-- >>> СКРИНШОТ 4: выполните блок создания процедуры
-- (сообщение об успехе в Schemas → Procedures).
-- ============================================================
DELIMITER //
CREATE PROCEDURE get_product_info(
IN p_id INT,
OUT out_name VARCHAR(100),
OUT out_final_price DECIMAL(10,2),
OUT out_status VARCHAR(50)
)
BEGIN
DECLARE v_price DECIMAL(10,2);
DECLARE v_discount DECIMAL(5,2);
DECLARE v_qty INT;
SELECT name, price, discount_percent, quantity
INTO out_name, v_price, v_discount, v_qty
FROM products
WHERE id = p_id;
IF out_name IS NULL THEN
SET out_name = 'Товар не найден';
SET out_final_price = 0;
SET out_status = 'Не применимо';
ELSE
SET out_final_price = calc_final_price(v_price, v_discount);
IF v_qty > 10 THEN
SET out_status = 'В наличии';
ELSEIF v_qty >= 1 THEN
SET out_status = 'Мало';
ELSE
SET out_status = 'Нет в наличии';
END IF;
END IF;
END //
DELIMITER ;
-- >>> СКРИНШОТ 4 (продолжение): выполните по очереди три вызова
-- (id = 1, id = 3, id = 99) и сделайте скриншот
-- каждого результата.
SET @name = '', @price = 0, @status = '';
CALL get_product_info(1, @name, @price, @status);
SELECT @name AS name, @price AS final_price, @status AS status;
SET @name = '', @price = 0, @status = '';
CALL get_product_info(3, @name, @price, @status);
SELECT @name AS name, @price AS final_price, @status AS status;
SET @name = '', @price = 0, @status = '';
CALL get_product_info(99, @name, @price, @status);
SELECT @name AS name, @price AS final_price, @status AS status;
-- >>> ОТВЕТ НА КОНТРОЛЬНЫЙ ВОПРОС 3:
-- Параметры OUT позволяют вернуть из процедуры несколько
-- значений одновременно, тогда как SELECT внутри процедуры
-- возвращает результат только как результирующий набор.
-- Значения через OUT можно сохранить в переменные и использовать
-- дальше в вызывающем коде (в других запросах, приложениях,
-- вложенных вызовах). Это делает интерфейс процедуры явным:
-- сразу видно, какие данные она принимает, а какие возвращает.
-- ============================================================
-- ЗАДАНИЕ 4. Процедура report_total_value
-- >>> СКРИНШОТ 5: выполните блок создания процедуры
-- (сообщение об успехе в Schemas → Procedures).
-- ============================================================
DELIMITER //
CREATE PROCEDURE report_total_value()
BEGIN
DECLARE v_id INT DEFAULT 1;
DECLARE v_max_id INT;
DECLARE v_current_price DECIMAL(10,2);
DECLARE v_current_discount DECIMAL(5,2);
DECLARE v_total DECIMAL(15,2) DEFAULT 0;
SELECT MAX(id) INTO v_max_id FROM products;
WHILE v_id <= v_max_id DO
SELECT price, discount_percent
INTO v_current_price, v_current_discount
FROM products
WHERE id = v_id;
IF v_current_price IS NOT NULL THEN
SET v_total = v_total +
calc_final_price(v_current_price, v_current_discount);
END IF;
SET v_id = v_id + 1;
END WHILE;
SELECT v_total AS total_value_with_discount;
END //
DELIMITER ;
-- >>> СКРИНШОТ 5 (продолжение): выполните вызов процедуры и
-- проверочный SELECT SUM — результаты должны
-- совпасть. Сделайте скриншот.
CALL report_total_value();
SELECT SUM(calc_final_price(price, discount_percent)) AS total_value
FROM products;
-- >>> ОТВЕТ НА КОНТРОЛЬНЫЙ ВОПРОС 4:
-- Обычный SQL-запрос нужен, когда расчёт сводится к одной
-- агрегатной функции — он проще, быстрее и оптимизируется
-- сервером. Процедура с циклом оправдана, когда логика
-- итеративная и зависит от предыдущих шагов: накопление с
-- проверками, обработка по сложным правилам, вызовы других
-- процедур/функций, запись промежуточных результатов.
-- В нашем случае цикл — учебный пример; на практике ту же сумму
-- быстрее получить одним SELECT SUM(...).
-- ============================================================
-- САМОСТОЯТЕЛЬНАЯ РАБОТА. Вариант A (базовый)
-- >>> СКРИНШОТ 6: сначала выполните создание функции calc_vat,
-- затем создание процедуры apply_vat_report,
-- затем вызов процедуры. Сделайте скриншот
-- итогового результата.
-- ============================================================
DELIMITER //
CREATE FUNCTION calc_vat(p_amount DECIMAL(10,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
RETURN p_amount * 0.20;
END //
DELIMITER ;
DELIMITER //
CREATE PROCEDURE apply_vat_report()
BEGIN
SELECT id,
name,
price,
discount_percent,
calc_final_price(price, discount_percent) AS final_price,
calc_vat(calc_final_price(price, discount_percent)) AS vat_amount
FROM products;
END //
DELIMITER ;
CALL apply_vat_report();
-- ============================================================
-- САМОСТОЯТЕЛЬНАЯ РАБОТА. Вариант B (продвинутый)
-- >>> СКРИНШОТ 7: выполните создание процедуры reserve_stock,
-- затем три тестовых вызова. Сделайте скриншоты.
-- ============================================================
DELIMITER //
CREATE PROCEDURE reserve_stock(
IN p_product_id INT,
IN p_qty_to_reserve INT,
OUT out_success BOOLEAN,
OUT out_message VARCHAR(255)
)
BEGIN
DECLARE v_qty INT;
DECLARE v_name VARCHAR(100);
SELECT name, quantity INTO v_name, v_qty
FROM products
WHERE id = p_product_id;
IF v_name IS NULL THEN
SET out_success = FALSE;
SET out_message = CONCAT('Товар с id=', p_product_id, ' не найден');
ELSEIF v_qty >= p_qty_to_reserve THEN
UPDATE products
SET quantity = quantity - p_qty_to_reserve
WHERE id = p_product_id;
SET out_success = TRUE;
SET out_message = CONCAT('Зарезервировано ', p_qty_to_reserve,
' ед. товара "', v_name, '"');
ELSE
SET out_success = FALSE;
SET out_message = CONCAT('Недостаточно товара "', v_name,
'": на складе ', v_qty,
', запрошено ', p_qty_to_reserve);
END IF;
END //
DELIMITER ;
-- Тест 1: успешный резерв
CALL reserve_stock(1, 3, @ok, @msg);
SELECT @ok AS success, @msg AS message;
-- Тест 2: недостаточно товара
CALL reserve_stock(1, 100, @ok, @msg);
SELECT @ok AS success, @msg AS message;
-- Тест 3: товар не существует
CALL reserve_stock(999, 1, @ok, @msg);
SELECT @ok AS success, @msg AS message;
-- Проверка, что quantity уменьшился после теста 1
SELECT id, name, quantity FROM products WHERE id = 1;
-- ============================================================
-- КОНЕЦ ФАЙЛА
-- ============================================================