Hôm nay tụi mình sẽ cùng nhau phân tích cách xây dựng một thủ tục thực tế để xử lý đơn hàng. Nó gồm nhiều bước: kiểm tra dữ liệu, cập nhật trạng thái đơn hàng, và ghi log. Hãy tưởng tượng một nhà hàng, nơi đầu bếp, phục vụ và thu ngân phải phối hợp nhịp nhàng. Trong thủ tục này, tụi mình sẽ làm logic tương tự giữa các bước.
Mô tả bài toán của thủ tục
Thủ tục xử lý đơn hàng cần thực hiện các bước sau:
- Kiểm tra xem hàng cần thiết có sẵn trong kho không.
- Nếu hàng đủ, trừ số lượng khỏi kho.
- Cập nhật trạng thái đơn hàng thành "Đã xử lý".
- Ghi thông tin về thao tác thành công vào nhật ký (log).
- Nếu có bất kỳ lỗi nào thì rollback về ban đầu.
Triển khai thủ tục
Bước 1. Tạo schema và bảng để làm việc
Trước khi code thủ tục, tụi mình sẽ tạo các bảng mà nó sẽ thao tác.
Bảng orders — đơn hàng
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'
);
Bảng này lưu các đơn hàng. Mỗi đơn có khách hàng, id sản phẩm, số lượng và trạng thái (mặc định là "Chờ xử lý").
Bảng inventory — kho hàng
CREATE TABLE inventory (
product_id SERIAL PRIMARY KEY,
product_name TEXT NOT NULL UNIQUE,
stock INT NOT NULL CHECK (stock >= 0)
);
Bảng này là danh sách hàng trong kho. Mỗi sản phẩm có số lượng tồn kho hiện tại (stock).
Bảng order_logs — nhật ký thao tác
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
);
Nhật ký này sẽ dùng để ghi thông tin về trạng thái xử lý đơn hàng.
Bước 2. Cấu trúc của thủ tục
Đây là cấu trúc của thủ tục nhiều bước:
- Kiểm tra xem hàng yêu cầu có trong kho và đủ số lượng không.
- Nếu đủ hàng, giảm số lượng trong bảng
inventory. - Đổi trạng thái đơn hàng thành "Đã xử lý".
- Ghi kết quả thành công vào bảng
order_logs. - Xử lý lỗi có thể xảy ra và rollback nếu cần.
Bước 3. Viết thủ tục
Giờ tụi mình sẽ viết thủ tục process_order để thực hiện các bước trên.
CREATE OR REPLACE FUNCTION process_order(order_id INT)
RETURNS VOID AS $$
DECLARE
v_product_id INT;
v_quantity INT;
v_stock INT;
BEGIN
-- Bước 1: Lấy thông tin đơn hàng
SELECT product_id, quantity
INTO v_product_id, v_quantity
FROM orders
WHERE order_id = $1;
-- Kiểm tra đơn hàng có tồn tại không
IF NOT FOUND THEN
RAISE EXCEPTION 'Đơn hàng với ID % không tồn tại.', $1;
END IF;
-- Bước 2: Kiểm tra hàng trong kho
SELECT stock INTO v_stock
FROM inventory
WHERE product_id = v_product_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Sản phẩm với ID % không có trong kho.', v_product_id;
END IF;
IF v_stock < v_quantity THEN
RAISE EXCEPTION 'Không đủ hàng cho sản phẩm ID %. Yêu cầu: %, Có sẵn: %.',
v_product_id, v_quantity, v_stock;
END IF;
-- Bước 3: Giảm số lượng hàng trong kho
UPDATE inventory
SET stock = stock - v_quantity
WHERE product_id = v_product_id;
-- Bước 4: Cập nhật trạng thái đơn hàng thành 'Processed'
UPDATE orders
SET status = 'Processed'
WHERE order_id = $1;
-- Bước 5: Ghi log thành công
INSERT INTO order_logs (order_id, log_message)
VALUES ($1, 'Đơn hàng đã xử lý thành công.');
EXCEPTION
WHEN OTHERS THEN
-- Ghi log lỗi nếu có bất kỳ thất bại nào
INSERT INTO order_logs (order_id, log_message)
VALUES ($1, 'Lỗi khi xử lý đơn hàng: ' || SQLERRM);
-- Rollback tất cả thay đổi
RAISE;
END;
$$ LANGUAGE plpgsql;
Cùng phân tích thủ tục này nhé.
Bước kiểm tra:
tụi mình kiểm tra xem đơn hàng có tồn tại trong bảng
orderskhông. Nếu không tìm thấy, sẽ raise exception với thông báo chi tiết. Tương tự, kiểm tra hàng trong kho và số lượng.Bước thao tác với kho:
nếu đủ hàng, giảm số lượng trong kho bằng lệnh
UPDATE.Bước đổi trạng thái đơn hàng:
đổi trạng thái thành "Processed" (Đã xử lý) để đánh dấu đơn đã hoàn thành.
Bước ghi log:
sau khi xử lý thành công, thêm thông báo vào bảng
order_logsđể lưu lại thao tác.Xử lý exception:
nếu có lỗi, tụi mình bắt lỗi trong block
EXCEPTION, ghi log chi tiết về lỗi và rollback mọi thay đổi.
Ví dụ sử dụng
Tạo dữ liệu test để kiểm tra thủ tục của tụi mình.
-- Thêm hàng vào kho
INSERT INTO inventory (product_name, stock)
VALUES ('Laptop', 10), ('Monitor', 5);
-- Thêm đơn hàng
INSERT INTO orders (customer_name, product_id, quantity)
VALUES
('Alice', 1, 2),
('Bob', 2, 1),
('Charlie', 1, 20); -- Đơn này sẽ gây lỗi
Giờ test thủ tục nhé:
-- Xử lý đơn của Alice
SELECT process_order(1);
-- Xử lý đơn của Bob
SELECT process_order(2);
-- Thử xử lý đơn của Charlie (lỗi)
SELECT process_order(3);
Kết quả:
- Đơn của Alice và Bob sẽ được xử lý thành công, ghi log và số lượng kho giảm đi.
- Đơn của Charlie sẽ gây lỗi vì không đủ hàng trong kho, và log lỗi sẽ được ghi lại.
Kiểm tra các bảng sau khi chạy lệnh:
SELECT * FROM inventory; -- Thay đổi số lượng kho
SELECT * FROM orders; -- Thay đổi trạng thái đơn hàng
SELECT * FROM order_logs; -- Ghi log thao tác
Lỗi thường gặp và mẹo
Lỗi: quên kiểm tra
NOT FOUNDsauSELECT INTO.Luôn xử lý trường hợp truy vấn trả về rỗng, nếu không sẽ bị exception bất ngờ.
Lỗi: không thêm block
EXCEPTION.Nếu không có xử lý lỗi trong thủ tục, khi exception xảy ra transaction có thể bị treo hoặc sai logic.
Mẹo: bảo vệ khỏi SQL injection.
Dùng tham số kiểu dữ liệu chặt chẽ và tránh SQL động nếu không cần thiết.
Mở rộng thủ tục
Trong thực tế, bạn có thể thêm nhiều kiểm tra hơn, ví dụ:
- Tính khuyến mãi hoặc giảm giá cho khách hàng.
- Kiểm tra hạn mức tín dụng của khách trước khi xử lý đơn.
- Ghi log cả khi rollback chứ không chỉ khi thành công.
GO TO FULL VERSION