Загрузка данных
-- ============================================================================
-- НАЧАЛЬНАЯ СТРУКТУРА БАЗЫ ДАННЫХ (на основе схемы интернет-магазина)
-- ============================================================================
DROP DATABASE IF EXISTS online_store;
CREATE DATABASE IF NOT EXISTS online_store;
USE online_store;
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL DEFAULT 0
);
CREATE TABLE customers (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
bonus_points INT DEFAULT 0
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT NOT NULL,
order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
total_amount DECIMAL(10,2) DEFAULT 0,
status ENUM('Создан', 'Оплачен', 'Отправлен', 'Доставлен', 'Отменен') DEFAULT 'Создан',
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
CREATE TABLE order_items (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id)
);
CREATE TABLE price_history (
id INT PRIMARY KEY AUTO_INCREMENT,
product_id INT NOT NULL,
old_price DECIMAL(10,2),
new_price DECIMAL(10,2),
change_date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (product_id) REFERENCES products(id)
);
CREATE TABLE deleted_products_archive (
id INT PRIMARY KEY AUTO_INCREMENT,
original_product_id INT,
name VARCHAR(100),
price DECIMAL(10,2),
stock INT,
deleted_date DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Наполнение тестовыми данными
INSERT INTO customers (name, bonus_points) VALUES
('Иван Петров', 0), ('Мария Сидорова', 150), ('Алексей Смирнов', 0), ('Елена Козлова', 300), ('Дмитрий Иванов', 50);
INSERT INTO products (name, price, stock) VALUES
('Ноутбук Lenovo', 45000.00, 10), ('Мышь Logitech', 1200.00, 50), ('Клавиатура Razer', 5500.00, 15),
('Монитор Samsung', 18900.00, 8), ('Наушники Sony', 8900.00, 20), ('Веб-камера Logitech', 3400.00, 12),
('SSD диск 1TB', 7200.00, 25), ('USB флешка 64GB', 800.00, 100);
INSERT INTO orders (customer_id, order_date, status) VALUES
(1, '2025-01-10 14:30:00', 'Доставлен'), (2, '2025-01-12 10:15:00', 'Отправлен'),
(3, '2025-01-15 16:45:00', 'Создан'), (1, '2025-01-18 09:20:00', 'Оплачен'),
(4, '2025-01-20 11:00:00', 'Создан'), (5, '2025-01-22 13:30:00', 'Создан');
-- ============================================================================
-- РЕАЛИЗАЦИЯ ТРИГГЕРОВ ПО ЗАДАНИЮ
-- ============================================================================
DELIMITER $$
-- ----------------------------------------------------------------------------
-- ЗАДАНИЕ №1: Автоматическая проверка остатка и уменьшение количества на складе
-- ----------------------------------------------------------------------------
-- Триггер проверяет наличие товара ДО добавления позиции в заказ.
CREATE TRIGGER before_order_items_insert
BEFORE INSERT ON order_items
FOR EACH ROW
BEGIN
DECLARE current_stock INT;
SELECT stock INTO current_stock
FROM products
WHERE id = NEW.product_id;
-- Если товара на складе меньше, чем запрашивается, выбрасываем ошибку
IF current_stock < NEW.quantity THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Ошибка оформления заказа: Недостаточно товара на складе!';
END IF;
END$$
-- Триггер уменьшает склад и пересчитывает сумму ПОСЛЕ успешной проверки и добавления
CREATE TRIGGER after_order_items_insert
AFTER INSERT ON order_items
FOR EACH ROW
BEGIN
-- Уменьшаем количество товара на складе
UPDATE products
SET stock = stock - NEW.quantity
WHERE id = NEW.product_id;
-- (ЗАДАНИЕ №3): Автоматический пересчет суммы заказа при добавлении товара
UPDATE orders
SET total_amount = (SELECT COALESCE(SUM(quantity * price), 0) FROM order_items WHERE order_id = NEW.order_id)
WHERE id = NEW.order_id;
END$$
-- ----------------------------------------------------------------------------
-- ЗАДАНИЕ №2: Отслеживание и логирование изменения стоимости товара
-- ----------------------------------------------------------------------------
CREATE TRIGGER after_products_update_price
AFTER UPDATE ON products
FOR EACH ROW
BEGIN
-- Записываем историю только если цена действительно изменилась
IF OLD.price <> NEW.price THEN
INSERT INTO price_history (product_id, old_price, new_price, change_date)
VALUES (OLD.id, OLD.price, NEW.price, NOW());
END IF;
END$$
-- ----------------------------------------------------------------------------
-- ЗАДАНИЕ №3: Пересчет итоговой суммы заказа при изменении количества или удалении
-- ----------------------------------------------------------------------------
-- Пересчет при обновлении количества/цены в позиции заказа
CREATE TRIGGER after_order_items_update
AFTER UPDATE ON order_items
FOR EACH ROW
BEGIN
-- Обновляем сумму текущего заказа
UPDATE orders
SET total_amount = (SELECT COALESCE(SUM(quantity * price), 0) FROM order_items WHERE order_id = NEW.order_id)
WHERE id = NEW.order_id;
-- На случай, если позиция была ошибочно перенесена в другой заказ, обновляем и старый заказ
IF OLD.order_id <> NEW.order_id THEN
UPDATE orders
SET total_amount = (SELECT COALESCE(SUM(quantity * price), 0) FROM order_items WHERE order_id = OLD.order_id)
WHERE id = OLD.order_id;
END IF;
END$$
-- Пересчет при удалении позиции из заказа
CREATE TRIGGER after_order_items_delete
AFTER DELETE ON order_items
FOR EACH ROW
BEGIN
UPDATE orders
SET total_amount = (SELECT COALESCE(SUM(quantity * price), 0) FROM order_items WHERE order_id = OLD.order_id)
WHERE id = OLD.order_id;
END$$
-- ----------------------------------------------------------------------------
-- ЗАДАНИЕ №4: Архивирование информации о товаре перед его удалением
-- ----------------------------------------------------------------------------
CREATE TRIGGER before_products_delete_archive
BEFORE DELETE ON products
FOR EACH ROW
BEGIN
INSERT INTO deleted_products_archive (original_product_id, name, price, stock, deleted_date)
VALUES (OLD.id, OLD.name, OLD.price, OLD.stock, NOW());
END$$
-- ----------------------------------------------------------------------------
-- ЗАДАНИЕ №5: Контроль последовательности изменения статусов заказа
-- ----------------------------------------------------------------------------
CREATE TRIGGER before_orders_update_status
BEFORE UPDATE ON orders
FOR EACH ROW
BEGIN
-- Реализуем проверку только при смене статуса менеджером
IF OLD.status <> NEW.status THEN
-- Правило: Отмена заказа допустима только из статуса «Создан»
IF NEW.status = 'Отменен' AND OLD.status <> 'Создан' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Ошибка статуса: Отменить заказ можно только из состояния "Создан"!';
-- Правило: Из «Создан» можно перейти только в «Оплачен» или «Отменен»
ELSEIF OLD.status = 'Создан' AND NEW.status NOT IN ('Оплачен', 'Отменен') THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Ошибка статуса: Из статуса "Создан" разрешен переход только в "Оплачен" или "Отменен".';
-- Правило: Из «Оплачен» можно перейти только в «Отправлен»
ELSEIF OLD.status = 'Оплачен' AND NEW.status <> 'Отправлен' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Ошибка статуса: Оплаченный заказ должен быть переведен в статус "Отправлен".';
-- Правило: Из «Отправлен» можно перейти только в «Доставлен»
ELSEIF OLD.status = 'Отправлен' AND NEW.status <> 'Доставлен' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Ошибка статуса: Отправленный заказ может быть изменен только на "Доставлен".';
-- Правило: Из финальных статусов («Доставлен» или «Отменен») переходы запрещены
ELSEIF OLD.status IN ('Доставлен', 'Отменен') THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Ошибка статуса: Нельзя изменить статус закрытого или отмененного заказа.';
END IF;
END IF;
END$$
-- ----------------------------------------------------------------------------
-- ЗАДАНИЕ №6: Программа лояльности (начисление бонусов)
-- ----------------------------------------------------------------------------
-- Логично начислять бонусы клиенту в тот момент, когда заказ переходит в статус 'Оплачен'
CREATE TRIGGER after_orders_update_bonuses
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
IF OLD.status <> NEW.status AND NEW.status = 'Оплачен' AND NEW.total_amount >= 1000 THEN
UPDATE customers
SET bonus_points = bonus_points + (FLOOR(NEW.total_amount / 1000) * 50)
WHERE id = NEW.customer_id;
END IF;
END$$
DELIMITER ;
-- 1. Проверка Заданий №1 и №3 (Попытка заказать слишком много товара - вызовет ошибку)
-- На складе Ноутбуков Lenovo всего 10 шт. Пытаемся заказать 15 шт:
INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (3, 1, 15, 45000.00);
-- Успешное добавление (Закажем 2 шт. Склад уменьшится с 10 до 8, а total_amount в заказе №3 пересчитается)
INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (3, 1, 2, 45000.00);
SELECT * FROM products WHERE id = 1; -- Проверка остатка
SELECT * FROM orders WHERE id = 3; -- Проверка пересчета суммы (станет 90000.00)
-- 2. Проверка Задания №2 (Изменение цены товара)
UPDATE products SET price = 48000.00 WHERE id = 1;
SELECT * FROM price_history; -- Появится запись о старой цене 45000 и новой 48000
-- 3. Проверка Задания №4 (Удаление товара и проверка архива)
DELETE FROM products WHERE id = 8;
SELECT * FROM deleted_products_archive; -- Появится запись об удаленной USB-флешке
-- 4. Проверка Задания №5 (Нарушение последовательности статусов - вызовет ошибку)
-- Заказ №5 сейчас в статусе 'Создан'. Пытаемся сразу перевести его в 'Доставлен':
UPDATE orders SET status = 'Доставлен' WHERE id = 5;
-- 5. Проверка Заданий №5 и №6 (Корректный перевод в 'Оплачен' + начисление бонусов)
-- Для заказа №3 сумма равна 90000.00. При оплате клиенту №3 (Алексей Смирнов) должно начислиться: 90 * 50 = 4500 бонусов.
UPDATE orders SET status = 'Оплачен' WHERE id = 3;
SELECT * FROM customers WHERE id = 3; -- Проверяем баланс бонусов Алексея