Сьогодні будемо розбирати побудову реальної процедури для обробки замовлень. Вона включає кілька етапів: валідацію даних, оновлення статусу замовлення, а також логування. Уяви собі ресторан, де шеф-кухар, офіціант і касир мають діяти злагоджено. У нашій процедурі ми реалізуємо схожу логіку взаємодії між етапами.
Опис завдання процедури
Процедура для обробки замовлень повинна виконувати такі етапи:
- Перевірити, чи доступний потрібний товар на складі.
- Якщо товару достатньо, списати його кількість зі складу.
- Оновити статус замовлення, щоб він став "Оброблено".
- Записати інформацію про успішну операцію в журнал (лог).
- При виникненні будь-якої помилки відкотити зміни до початку.
Реалізація процедури
Крок 1. Створюємо схему і таблиці для роботи
Перш ніж писати процедуру, створимо таблиці, з якими вона буде працювати.
Таблиця orders — замовлення
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_name TEXT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL CHECK (quantity > 0),
status TEXT DEFAULT 'Pending'
);
Ця таблиця зберігає замовлення. У кожного замовлення є клієнт, ідентифікатор продукту, кількість і статус (за замовчуванням "Очікує обробки").
Таблиця inventory — склад
CREATE TABLE inventory (
product_id SERIAL PRIMARY KEY,
product_name TEXT NOT NULL UNIQUE,
stock INT NOT NULL CHECK (stock >= 0)
);
Таблиця зі списком товарів на складі. Кожен продукт має поточний запас (stock).
Таблиця order_logs — журнал операцій
CREATE TABLE order_logs (
log_id SERIAL PRIMARY KEY,
order_id INT NOT NULL REFERENCES orders(order_id) ON DELETE CASCADE,
log_message TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Журнал буде використовуватись для запису інформації про статус виконання замовлень.
Крок 2. Структура процедури
Ось структура багатокрокової процедури:
- Перевірити, чи є запитуваний товар на складі і чи достатньо його.
- Якщо товару достатньо, зменшити його кількість у таблиці
inventory. - Змінити статус замовлення на "Оброблено".
- Записати успішний результат у таблицю
order_logs. - Обробити можливі помилки з відкотом змін.
Крок 3. Написання процедури
Давай напишемо процедуру process_order для виконання описаних вище кроків.
CREATE OR REPLACE FUNCTION process_order(order_id INT)
RETURNS VOID AS $$
DECLARE
v_product_id INT;
v_quantity INT;
v_stock INT;
BEGIN
-- Крок 1: Отримуємо інформацію про замовлення
SELECT product_id, quantity
INTO v_product_id, v_quantity
FROM orders
WHERE order_id = $1;
-- Перевіряємо, чи існує замовлення
IF NOT FOUND THEN
RAISE EXCEPTION 'Замовлення з ID % не існує.', $1;
END IF;
-- Крок 2: Перевіряємо наявність товару на складі
SELECT stock INTO v_stock
FROM inventory
WHERE product_id = v_product_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Товар з ID % не існує на складі.', v_product_id;
END IF;
IF v_stock < v_quantity THEN
RAISE EXCEPTION 'Недостатньо товару для продукту ID %. Запрошено: %, Доступно: %.',
v_product_id, v_quantity, v_stock;
END IF;
-- Крок 3: Зменшуємо кількість товару на складі
UPDATE inventory
SET stock = stock - v_quantity
WHERE product_id = v_product_id;
-- Крок 4: Оновлюємо статус замовлення на 'Processed'
UPDATE orders
SET status = 'Processed'
WHERE order_id = $1;
-- Крок 5: Записуємо успішне виконання в журнал
INSERT INTO order_logs (order_id, log_message)
VALUES ($1, 'Замовлення успішно оброблено.');
EXCEPTION
WHEN OTHERS THEN
-- Логуємо помилку при будь-якій невдачі
INSERT INTO order_logs (order_id, log_message)
VALUES ($1, 'Помилка при обробці замовлення: ' || SQLERRM);
-- Відкатуємо всі зміни
RAISE;
END;
$$ LANGUAGE plpgsql;
Давай розберемо цю процедуру.
Етап перевірки:
ми перевіряємо, чи існує вказане замовлення у таблиці
orders. Якщо замовлення не знайдено, викликається виключення з детальним повідомленням. Аналогічно, перевіряємо наявність і кількість товару на складі.Етап роботи зі складом:
якщо товару достатньо, зменшуємо його кількість на складі. Це робиться через
UPDATE.Етап зміни статусу замовлення:
змінюємо статус на "Processed" (Оброблено), щоб показати, що замовлення успішно завершене.
Етап логування:
після успішної обробки замовлення додаємо повідомлення у таблицю
order_logs, щоб зберегти інформацію про операцію.Обробка виключень:
якщо щось йде не так, ми перехоплюємо помилку у блоці
EXCEPTION, записуємо у лог детальне повідомлення про помилку і відкатуємо всі зміни.
Приклади використання
Створимо тестові дані для перевірки роботи нашої процедури.
-- Додаємо товар на склад
INSERT INTO inventory (product_name, stock)
VALUES ('Laptop', 10), ('Monitor', 5);
-- Додаємо замовлення
INSERT INTO orders (customer_name, product_id, quantity)
VALUES
('Аліса', 1, 2),
('Боб', 2, 1),
('Чарлі', 1, 20); -- Це замовлення має викликати помилку
Тепер протестуємо процедуру:
-- Обробляємо замовлення Аліси
SELECT process_order(1);
-- Обробляємо замовлення Боба
SELECT process_order(2);
-- Пробуємо обробити замовлення Чарлі (помилка)
SELECT process_order(3);
Результати:
- Замовлення Аліси і Боба будуть успішно оброблені, запишуться у лог, а залишки на складі зменшаться.
- Замовлення Чарлі викличе помилку через недостатню кількість товару на складі, і запис про помилку з'явиться у журналі.
Перевіримо таблиці після виконання запитів:
SELECT * FROM inventory; -- Зміни у запасах
SELECT * FROM orders; -- Зміни у статусах замовлень
SELECT * FROM order_logs; -- Записи у журналі
Типові помилки і поради
Помилка: забули перевірити
NOT FOUNDпісляSELECT INTO.Завжди обробляй випадки, коли запит повертає порожній результат, інакше це призведе до неочікуваних виключень.
Помилка: не додали
EXCEPTION-блок.Якщо у процедурі не передбачено обробник помилок, при виключенні транзакція може зависнути або порушити логіку виконання.
Порада: захищайся від SQL-ін'єкцій.
Використовуй строго типізовані параметри і уникай динамічного SQL, якщо це не потрібно.
Розширення процедури
У реальному житті можна додати більше перевірок, наприклад:
- Враховувати знижки або акції для клієнтів.
- Перевіряти кредитний ліміт клієнта перед обробкою замовлення.
- Логувати не тільки успішні операції, а й відкати.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ