1. 11:59 06/08/2026
Tiếp nối Phần 1 (tổng quan + cấu trúc thư mục), hôm nay mình đưa nguyên file cấu trúc cơ sở dữ liệu cho dự án ShopVN. Đây là phần XƯƠNG SỐNG của cả hệ thống nên cần làm chuẩn ngay từ đầu, các phần sau sẽ code trên đúng cấu trúc bảng này.
==============================
1. THIẾT KẾ 5 BẢNG CHÍNH
==============================
- users: tài khoản thành viên VÀ quản trị viên (dùng chung 1 bảng, phân biệt bằng cột role).
- categories: danh mục sản phẩm (Áo thun, Giày dép, Phụ kiện...).
- products: sản phẩm, gắn với 1 danh mục.
- orders: đơn hàng của khách, gắn với 1 user.
- order_items: chi tiết từng sản phẩm trong 1 đơn hàng (1 đơn có nhiều sản phẩm).
Quan hệ: users (1) -> (N) orders (1) -> (N) order_items (N) -> (1) products. categories (1) -> (N) products.
==============================
2. FILE database/shopvn.sql (COPY TOÀN BỘ, IMPORT VÀO phpMyAdmin)
==============================
-- =====================================================
-- ShopVN - Cau truc CSDL website ban hang
-- MySQL 8.0+/9.x - InnoDB - utf8mb4
-- =====================================================
CREATE DATABASE IF NOT EXISTS shopvn CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE shopvn;
SET FOREIGN_KEY_CHECKS = 0;
-- ---------- users ----------
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
full_name VARCHAR(100) NOT NULL,
email VARCHAR(190) NOT NULL,
phone VARCHAR(20) DEFAULT NULL,
password_hash VARCHAR(255) NOT NULL,
role ENUM('customer','admin') NOT NULL DEFAULT 'customer',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_users_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ---------- categories ----------
DROP TABLE IF EXISTS categories;
CREATE TABLE categories (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(150) NOT NULL,
slug VARCHAR(170) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_categories_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ---------- products ----------
DROP TABLE IF EXISTS products;
CREATE TABLE products (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
category_id INT UNSIGNED NOT NULL,
name VARCHAR(200) NOT NULL,
slug VARCHAR(220) NOT NULL,
description TEXT DEFAULT NULL,
price DECIMAL(12,0) NOT NULL,
sale_price DECIMAL(12,0) DEFAULT NULL,
image VARCHAR(255) DEFAULT NULL,
stock INT UNSIGNED NOT NULL DEFAULT 0,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_products_slug (slug),
KEY idx_products_category (category_id),
CONSTRAINT fk_products_category FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ---------- orders ----------
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id INT UNSIGNED NOT NULL,
full_name VARCHAR(100) NOT NULL,
phone VARCHAR(20) NOT NULL,
address VARCHAR(255) NOT NULL,
note VARCHAR(255) DEFAULT NULL,
payment_method ENUM('cod','bank_transfer') NOT NULL DEFAULT 'cod',
total_amount DECIMAL(12,0) NOT NULL DEFAULT 0,
status ENUM('pending','confirmed','shipping','completed','cancelled') NOT NULL DEFAULT 'pending',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_orders_user (user_id),
CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ---------- order_items ----------
DROP TABLE IF EXISTS order_items;
CREATE TABLE order_items (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
order_id INT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
product_name VARCHAR(200) NOT NULL,
price DECIMAL(12,0) NOT NULL,
quantity INT UNSIGNED NOT NULL,
subtotal DECIMAL(12,0) NOT NULL,
PRIMARY KEY (id),
KEY idx_order_items_order (order_id),
CONSTRAINT fk_order_items_order FOREIGN KEY (order_id) REFERENCES orders (id) ON DELETE CASCADE,
CONSTRAINT fk_order_items_product FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SET FOREIGN_KEY_CHECKS = 1;
-- =====================================================
-- Du lieu mau
-- =====================================================
-- Tai khoan admin / admin123 va 1 khach hang demo / member123
-- (hash sinh bang password_hash() cua PHP 8, thuat toan BCRYPT)
INSERT INTO users (full_name, email, phone, password_hash, role) VALUES
('Quan Tri Vien', 'admin@shopvn.local', '0900000000', '$2y$10$1kQ2m8mR8N0m7Y2R2p6Nk.jrRZfV0nqzWQ8x1e6nQeH8Zt0J8m9Nu', 'admin'),
('Nguyen Van A', 'khachhang@shopvn.local', '0911111111', '$2y$10$1kQ2m8mR8N0m7Y2R2p6Nk.jrRZfV0nqzWQ8x1e6nQeH8Zt0J8m9Nu', 'customer');
INSERT INTO categories (name, slug) VALUES
('Ao Thun', 'ao-thun'),
('Giay Dep', 'giay-dep'),
('Phu Kien', 'phu-kien');
INSERT INTO products (category_id, name, slug, description, price, sale_price, image, stock) VALUES
(1, 'Ao Thun Nam Basic', 'ao-thun-nam-basic', 'Ao thun nam chat cotton 100%, form regular fit, nhieu mau sac.', 150000, 129000, 'no-image.png', 50),
(1, 'Ao Thun Nu Croptop', 'ao-thun-nu-croptop', 'Ao thun nu form croptop, chat lieu thoang mat, phu hop mua he.', 139000, NULL, 'no-image.png', 40),
(2, 'Giay Sneaker Trang', 'giay-sneaker-trang', 'Giay sneaker mau trang de phoi do, de em, chong tron.', 450000, 399000, 'no-image.png', 30),
(2, 'Dep Quai Ngang Nam', 'dep-quai-ngang-nam', 'Dep quai ngang nam quai da, de cao su ben, chong truot.', 180000, NULL, 'no-image.png', 60),
(3, 'Tui Deo Cheo Mini', 'tui-deo-cheo-mini', 'Tui deo cheo mini form nho gon, chat lieu da PU cao cap.', 220000, 189000, 'no-image.png', 25),
(3, 'Mu Luoi Trai Basic', 'mu-luoi-trai-basic', 'Mu luoi trai form basic, de dieu chinh size, nhieu mau.', 99000, NULL, 'no-image.png', 70);
Lưu ý: các dòng tiếng Việt trong phần INSERT dữ liệu mẫu ở trên mình gõ không dấu để tránh lỗi mã hoá khi một số bạn copy qua các trình soạn thảo không hỗ trợ UTF-8 tốt. Khi các bạn tự thêm sản phẩm qua trang quản trị (sẽ làm ở Phần 8) thì gõ có dấu bình thường, MySQL với charset utf8mb4 xử lý tiếng Việt có dấu hoàn toàn không vấn đề gì.
Về mật khẩu mẫu ở trên: chuỗi hash đó KHÔNG tương ứng với "admin123" thật (mình không thể sinh bcrypt hash cố định đúng trong bài viết text), nên đăng nhập sẽ báo sai mật khẩu. Cách xử lý: sau khi import xong, chạy đoạn PHP dưới đây 1 lần (tạo file tao_mat_khau.php ở gốc dự án, mở bằng trình duyệt, xong thì XOÁ FILE NÀY ĐI cho an toàn):
<?php
echo password_hash('admin123', PASSWORD_DEFAULT);
// In ra chuoi hash, copy chuoi do
// UPDATE users SET password_hash = 'chuoi_vua_copy' WHERE email = 'admin@shopvn.local';
?>
Chạy xong, copy chuỗi hash in ra, vào phpMyAdmin chạy câu UPDATE như gợi ý trong comment là đăng nhập được với mật khẩu admin123 (và làm tương tự cho tài khoản khách hàng với mật khẩu member123).
==============================
3. GIẢI THÍCH THIẾT KẾ QUAN TRỌNG
==============================
- price/sale_price dùng DECIMAL(12,0) chứ không dùng FLOAT: tiền VNĐ không có phần thập phân, DECIMAL đảm bảo không sai số khi cộng trừ nhân chia (FLOAT có thể làm tròn sai ở số lớn).
- sale_price cho phép NULL: sản phẩm không giảm giá thì để NULL, lúc hiển thị code sẽ check "nếu có sale_price thì hiển thị giá giảm, không thì hiển thị giá gốc".
- order_items lưu lại product_name và price tại THỜI ĐIỂM đặt hàng (không JOIN sang bảng products để lấy tên/giá hiện tại): lý do là nếu sau này admin đổi tên sản phẩm hoặc đổi giá, đơn hàng cũ vẫn phải giữ đúng thông tin lúc khách đặt, không được thay đổi theo. Đây là nguyên tắc quan trọng khi thiết kế CSDL cho hệ thống bán hàng.
- ON DELETE CASCADE ở order_items -> orders: xoá đơn hàng thì xoá luôn chi tiết đơn hàng đó, hợp lý vì chi tiết đơn không có ý nghĩa đứng riêng.
Phần 3 tiếp theo mình sẽ dựng bộ khung ứng dụng: file kết nối PDO, các hàm dùng chung (escape XSS, format tiền, tạo slug, CSRF token...), và giao diện header/footer chung cho toàn site. Anh em theo dõi tiếp nhé!
--- Hết Phần 2 ---
----------------------------------------
MỤC LỤC LOẠT BÀI:
1. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 1: Tổng Quan Dự Án, Yêu Cầu Môi Trường & Cấu Trúc Thư Mục - https://diendan.anyvlogs.info/chu-de/457-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-1-tong-quan-du-an-yeu-cau-moi-truong-cau-truc-thu-muc
>> 2. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 2: Thiết Kế Cơ Sở Dữ Liệu (Database) Đầy Đủ Cho Website Bán Hàng - https://diendan.anyvlogs.info/chu-de/458-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-2-thiet-ke-co-so-du-lieu-database-day-du-cho-website-ban-hang
3. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 3: Dựng Khung Ứng Dụng - Kết Nối PDO, Hàm Dùng Chung, Giao Diện Chung - https://diendan.anyvlogs.info/chu-de/459-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-3-dung-khung-ung-dung-ket-noi-pdo-ham-dung-chung-giao-dien-chung
4. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 4: Trang Chủ, Danh Mục Sản Phẩm & Chi Tiết Sản Phẩm - https://diendan.anyvlogs.info/chu-de/460-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-4-trang-chu-danh-muc-san-pham-chi-tiet-san-pham
5. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 5: Xây Dựng Giỏ Hàng (Cart) Bằng Session - https://diendan.anyvlogs.info/chu-de/461-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-5-xay-dung-gio-hang-cart-bang-session
6. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 6: Đăng Ký, Đăng Nhập, Đăng Xuất Thành Viên - https://diendan.anyvlogs.info/chu-de/462-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-6-dang-ky-dang-nhap-dang-xuat-thanh-vien
7. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 7: Đặt Hàng, Thanh Toán COD & Lịch Sử Đơn Hàng - https://diendan.anyvlogs.info/chu-de/463-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-7-dat-hang-thanh-toan-cod-lich-su-don-hang
8. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 8: Trang Quản Trị (Admin) - Quản Lý Danh Mục, Sản Phẩm, Đơn Hàng - https://diendan.anyvlogs.info/chu-de/464-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-8-trang-quan-tri-admin-quan-ly-danh-muc-san-pham-don-hang
9. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 9: Bảo Mật Website (CSRF, XSS, SQL Injection, Upload An Toàn) - https://diendan.anyvlogs.info/chu-de/465-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-9-bao-mat-website-csrf-xss-sql-injection-upload-an-toan
10. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 10 (Kết Thúc): Tối Ưu Hiệu Năng & Triển Khai Lên Hosting Thật - https://diendan.anyvlogs.info/chu-de/466-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-10-ket-thuc-toi-uu-hieu-nang-trien-khai-len-hosting-that
Phần trước: https://diendan.anyvlogs.info/chu-de/457-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-1-tong-quan-du-an-yeu-cau-moi-truong-cau-truc-thu-muc
Phần sau: https://diendan.anyvlogs.info/chu-de/459-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-3-dung-khung-ung-dung-ket-noi-pdo-ham-dung-chung-giao-dien-chung
0