# Bài Tập SQL Cơ Bản Cho Tester
## 15 Bài Thực Hành Từ Cơ Bản Đến Nâng Cao

---

# PHẦN 1: CHUẨN BỊ - SCHEMA VÀ DỮ LIỆU MẪU

## 1.1 Tạo Bảng (Schema)

```sql
-- ==========================================
-- BẢNG 1: USERS (Người dùng)
-- ==========================================
CREATE TABLE users (
    user_id     INT PRIMARY KEY,
    full_name   VARCHAR(100),
    email       VARCHAR(100),
    age         INT,
    city        VARCHAR(50),
    status      VARCHAR(20),   -- 'active' hoặc 'inactive'
    created_at  DATE
);

-- ==========================================
-- BẢNG 2: PRODUCTS (Sản phẩm)
-- ==========================================
CREATE TABLE products (
    product_id    INT PRIMARY KEY,
    product_name  VARCHAR(100),
    category      VARCHAR(50),
    price         DECIMAL(10,2),
    stock         INT,
    is_available  BOOLEAN
);

-- ==========================================
-- BẢNG 3: ORDERS (Đơn hàng)
-- ==========================================
CREATE TABLE orders (
    order_id    INT PRIMARY KEY,
    user_id     INT,
    product_id  INT,
    quantity    INT,
    total_price DECIMAL(10,2),
    status      VARCHAR(20),   -- 'pending', 'completed', 'cancelled'
    order_date  DATE,
    FOREIGN KEY (user_id)    REFERENCES users(user_id),
    FOREIGN KEY (product_id) REFERENCES products(product_id)
);
```

---

## 1.2 Dữ Liệu Mẫu

```sql
-- ==========================================
-- DỮ LIỆU BẢNG USERS
-- ==========================================
INSERT INTO users VALUES
(1,  'Nguyễn Văn An',    'an.nguyen@email.com',    25, 'Hà Nội',    'active',   '2023-01-15'),
(2,  'Trần Thị Bình',    'binh.tran@email.com',    30, 'HCM',       'active',   '2023-02-20'),
(3,  'Lê Văn Cường',     'cuong.le@email.com',     22, 'Đà Nẵng',   'inactive', '2023-03-10'),
(4,  'Phạm Thị Dung',    'dung.pham@email.com',    35, 'HCM',       'active',   '2023-01-05'),
(5,  'Hoàng Văn Em',     'em.hoang@email.com',     28, 'Hà Nội',    'active',   '2023-04-18'),
(6,  'Vũ Thị Fang',      'fang.vu@email.com',      45, 'Hải Phòng', 'inactive', '2022-12-01'),
(7,  'Đặng Văn Giang',   'giang.dang@email.com',   33, 'Hà Nội',    'active',   '2023-05-22'),
(8,  'Bùi Thị Hoa',      'hoa.bui@email.com',      27, 'HCM',       'active',   '2023-06-30'),
(9,  'Ngô Văn Inh',      'inh.ngo@email.com',      19, 'Đà Nẵng',   'active',   '2023-07-14'),
(10, 'Đinh Thị Kiều',    'kieu.dinh@email.com',    42, 'Hà Nội',    'inactive', '2022-11-08');

-- ==========================================
-- DỮ LIỆU BẢNG PRODUCTS
-- ==========================================
INSERT INTO products VALUES
(1,  'iPhone 15',         'Điện thoại',  25000000, 50,  true),
(2,  'Samsung Galaxy S23','Điện thoại',  20000000, 30,  true),
(3,  'MacBook Pro',       'Laptop',      45000000, 20,  true),
(4,  'Dell XPS 13',       'Laptop',      32000000, 15,  true),
(5,  'iPad Air',          'Máy tính bảng',18000000,25,  true),
(6,  'AirPods Pro',       'Phụ kiện',    6000000,  100, true),
(7,  'Samsung Buds',      'Phụ kiện',    3500000,  80,  true),
(8,  'Màn hình LG 27"',   'Màn hình',    8000000,  40,  true),
(9,  'Bàn phím Logitech', 'Phụ kiện',    1500000,  200, false),
(10, 'Chuột Razer',       'Phụ kiện',    2000000,  150, true);

-- ==========================================
-- DỮ LIỆU BẢNG ORDERS
-- ==========================================
INSERT INTO orders VALUES
(1,  1, 1,  1, 25000000, 'completed', '2024-01-10'),
(2,  1, 6,  2, 12000000, 'completed', '2024-01-15'),
(3,  2, 3,  1, 45000000, 'completed', '2024-01-20'),
(4,  2, 7,  1, 3500000,  'cancelled', '2024-02-01'),
(5,  3, 2,  1, 20000000, 'completed', '2024-02-05'),
(6,  4, 5,  2, 36000000, 'completed', '2024-02-10'),
(7,  4, 8,  1, 8000000,  'pending',   '2024-02-15'),
(8,  5, 1,  1, 25000000, 'completed', '2024-02-20'),
(9,  5, 4,  1, 32000000, 'pending',   '2024-03-01'),
(10, 6, 6,  3, 18000000, 'completed', '2024-03-05'),
(11, 7, 10, 2, 4000000,  'completed', '2024-03-10'),
(12, 7, 9,  1, 1500000,  'cancelled', '2024-03-15'),
(13, 8, 3,  1, 45000000, 'completed', '2024-03-20'),
(14, 9, 2,  1, 20000000, 'pending',   '2024-03-25'),
(15, 10,1,  1, 25000000, 'completed', '2024-04-01'),
(16, 1, 4,  1, 32000000, 'completed', '2024-04-05'),
(17, 2, 8,  2, 16000000, 'completed', '2024-04-10'),
(18, 3, 10, 1, 2000000,  'completed', '2024-04-15'),
(19, 8, 6,  1, 6000000,  'pending',   '2024-04-20'),
(20, 9, 5,  1, 18000000, 'completed', '2024-04-25');
```

---

## 1.3 Sơ Đồ Quan Hệ Bảng

```
┌─────────────────────┐         ┌──────────────────────────┐         ┌─────────────────────┐
│       USERS         │         │         ORDERS           │         │      PRODUCTS       │
├─────────────────────┤         ├──────────────────────────┤         ├─────────────────────┤
│ user_id   (PK) ─────┼────────►│ order_id    (PK)         │◄────────┼─ product_id  (PK)   │
│ full_name           │         │ user_id     (FK)         │         │ product_name        │
│ email               │         │ product_id  (FK)         │         │ category            │
│ age                 │         │ quantity                 │         │ price               │
│ city                │         │ total_price              │         │ stock               │
│ status              │         │ status                   │         │ is_available        │
│ created_at          │         │ order_date               │         └─────────────────────┘
└─────────────────────┘         └──────────────────────────┘
```

---

## 1.4 Giải Thích Nhanh Các Lệnh SQL Sẽ Dùng

| Lệnh | Dùng để làm gì | Ví dụ đơn giản |
|------|----------------|----------------|
| `SELECT` | Lấy dữ liệu từ bảng | `SELECT * FROM users` |
| `WHERE` | Lọc theo điều kiện | `WHERE age > 25` |
| `JOIN` | Kết hợp 2 bảng lại | `users JOIN orders ON ...` |
| `COUNT()` | Đếm số bản ghi | `COUNT(order_id)` |
| `GROUP BY` | Nhóm dữ liệu lại | `GROUP BY city` |
| `ORDER BY` | Sắp xếp kết quả | `ORDER BY age DESC` |
| `HAVING` | Lọc sau GROUP BY | `HAVING COUNT(*) > 2` |

---

# PHẦN 2: BÀI TẬP THỰC HÀNH

---

## 🟢 MỨC CƠ BẢN (Bài 1–5): Làm Quen Với SELECT và WHERE

---

### Bài 1: Xem Toàn Bộ Dữ Liệu

**📋 Yêu cầu:**
> Lấy toàn bộ thông tin của tất cả người dùng trong bảng `users`.

**💡 Gợi ý:**
- Dùng `SELECT *` để lấy tất cả cột
- Chỉ cần chỉ định tên bảng sau `FROM`

**🎯 Kết quả mong đợi:**
```
Hiển thị đủ 10 dòng, đủ 7 cột của bảng users
```

---

**✅ Đáp án:**

```sql
SELECT *
FROM users;
```

**📖 Giải thích:**
```
SELECT *        → Lấy TẤT CẢ các cột
FROM users      → Từ bảng users
```

**📊 Kết quả trả về:**
```
user_id | full_name          | email                  | age | city       | status   | created_at
--------|---------------------|------------------------|-----|------------|----------|------------
1       | Nguyễn Văn An      | an.nguyen@email.com    | 25  | Hà Nội     | active   | 2023-01-15
2       | Trần Thị Bình      | binh.tran@email.com    | 30  | HCM        | active   | 2023-02-20
3       | Lê Văn Cường       | cuong.le@email.com     | 22  | Đà Nẵng    | inactive | 2023-03-10
... (10 dòng)
```

---

### Bài 2: Chỉ Lấy Một Số Cột Cần Thiết

**📋 Yêu cầu:**
> Lấy danh sách sản phẩm, chỉ hiển thị: tên sản phẩm, danh mục và giá. Không cần lấy các cột khác.

**💡 Gợi ý:**
- Thay `*` bằng tên các cột cụ thể, ngăn cách nhau bằng dấu phẩy
- Tên cột: `product_name`, `category`, `price`

**🎯 Kết quả mong đợi:**
```
Hiển thị 10 sản phẩm, mỗi dòng có 3 cột: product_name, category, price
```

---

**✅ Đáp án:**

```sql
SELECT product_name,
       category,
       price
FROM products;
```

**📖 Giải thích:**
```
SELECT product_name, category, price  → Chỉ lấy 3 cột này
FROM products                         → Từ bảng products
```

**📊 Kết quả trả về:**
```
product_name        | category        | price
--------------------|-----------------|----------
iPhone 15           | Điện thoại      | 25000000
Samsung Galaxy S23  | Điện thoại      | 20000000
MacBook Pro         | Laptop          | 45000000
Dell XPS 13         | Laptop          | 32000000
iPad Air            | Máy tính bảng   | 18000000
... (10 dòng)
```

---

### Bài 3: Lọc Theo Điều Kiện Đơn Giản

**📋 Yêu cầu:**
> Tìm tất cả người dùng có status là `'active'`.

**💡 Gợi ý:**
- Dùng `WHERE` để lọc
- So sánh chuỗi dùng dấu `=` và bọc giá trị trong dấu nháy đơn `' '`

**🎯 Kết quả mong đợi:**
```
Hiển thị 7 người dùng có status = 'active'
```

---

**✅ Đáp án:**

```sql
SELECT *
FROM users
WHERE status = 'active';
```

**📖 Giải thích:**
```
SELECT *          → Lấy tất cả cột
FROM users        → Từ bảng users
WHERE             → Điều kiện lọc:
  status = 'active' → Chỉ lấy dòng có status bằng 'active'
```

**📊 Kết quả trả về:**
```
user_id | full_name        | email                  | age | city       | status | created_at
--------|------------------|------------------------|-----|------------|--------|------------
1       | Nguyễn Văn An   | an.nguyen@email.com    | 25  | Hà Nội     | active | 2023-01-15
2       | Trần Thị Bình   | binh.tran@email.com    | 30  | HCM        | active | 2023-02-20
4       | Phạm Thị Dung   | dung.pham@email.com    | 35  | HCM        | active | 2023-01-05
5       | Hoàng Văn Em    | em.hoang@email.com     | 28  | Hà Nội     | active | 2023-04-18
7       | Đặng Văn Giang  | giang.dang@email.com   | 33  | Hà Nội     | active | 2023-05-22
8       | Bùi Thị Hoa     | hoa.bui@email.com      | 27  | HCM        | active | 2023-06-30
9       | Ngô Văn Inh     | inh.ngo@email.com      | 19  | Đà Nẵng    | active | 2023-07-14
```

---

### Bài 4: Lọc Với Nhiều Điều Kiện (AND / OR)

**📋 Yêu cầu:**
> Tìm những người dùng ở thành phố `'Hà Nội'` VÀ có status `'active'`.

**💡 Gợi ý:**
- Dùng `AND` khi cần thỏa mãn **cả hai** điều kiện cùng lúc
- Dùng `OR`  khi chỉ cần thỏa mãn **một trong hai** điều kiện

**🎯 Kết quả mong đợi:**
```
Hiển thị 3 người dùng: Nguyễn Văn An, Hoàng Văn Em, Đặng Văn Giang
```

---

**✅ Đáp án:**

```sql
SELECT *
FROM users
WHERE city = 'Hà Nội'
  AND status = 'active';
```

**📖 Giải thích:**
```
WHERE city = 'Hà Nội'   → Điều kiện 1: sống ở Hà Nội
  AND status = 'active'  → Điều kiện 2: phải đồng thời là active
  ↑ AND = bắt buộc cả hai đều đúng
```

**📊 Kết quả trả về:**
```
user_id | full_name         | city    | status
--------|-------------------|---------|-------
1       | Nguyễn Văn An    | Hà Nội  | active
5       | Hoàng Văn Em     | Hà Nội  | active
7       | Đặng Văn Giang   | Hà Nội  | active
```

**🔁 Biến thể thêm (để học):**
```sql
-- Tìm user ở HCM HOẶC Đà Nẵng
SELECT * FROM users
WHERE city = 'HCM' OR city = 'Đà Nẵng';

-- Cách viết gọn hơn dùng IN
SELECT * FROM users
WHERE city IN ('HCM', 'Đà Nẵng');
```

---

### Bài 5: Lọc Theo Khoảng Số

**📋 Yêu cầu:**
> Tìm tất cả sản phẩm có giá từ `5.000.000` đến `25.000.000` đồng (bao gồm cả hai đầu).

**💡 Gợi ý:**
- Có thể dùng `>=` và `<=` kết hợp `AND`
- Hoặc dùng `BETWEEN ... AND ...` để viết gọn hơn

**🎯 Kết quả mong đợi:**
```
Hiển thị 5 sản phẩm trong khoảng giá đó
```

---

**✅ Đáp án:**

```sql
-- Cách 1: Dùng >= và <=
SELECT product_name, price
FROM products
WHERE price >= 5000000
  AND price <= 25000000;

-- Cách 2: Dùng BETWEEN (gọn hơn, kết quả như nhau)
SELECT product_name, price
FROM products
WHERE price BETWEEN 5000000 AND 25000000;
```

**📖 Giải thích:**
```
BETWEEN 5000000 AND 25000000
→ Lấy các giá trị: 5000000 ≤ price ≤ 25000000
→ Bao gồm cả hai đầu mút
```

**📊 Kết quả trả về:**
```
product_name        | price
--------------------|----------
iPhone 15           | 25000000
Samsung Galaxy S23  | 20000000
iPad Air            | 18000000
AirPods Pro         | 6000000
Màn hình LG 27"    | 8000000
```

---

## 🟡 MỨC TRUNG BÌNH (Bài 6–10): JOIN và Kết Hợp Bảng

---

### Bài 6: JOIN Cơ Bản — Xem Ai Đặt Đơn Hàng Nào

**📋 Yêu cầu:**
> Hiển thị danh sách đơn hàng kèm theo **tên người dùng** đã đặt. Chỉ lấy: tên người dùng, mã đơn hàng, ngày đặt, trạng thái đơn.

**💡 Gợi ý:**
- Bảng `orders` có `user_id` nhưng không có tên người dùng
- Bảng `users` có tên người dùng
- Cần `JOIN` hai bảng qua cột `user_id`

**🎯 Kết quả mong đợi:**
```
Hiển thị 20 dòng, mỗi dòng có: tên user + thông tin đơn hàng
```

---

**✅ Đáp án:**

```sql
SELECT u.full_name,
       o.order_id,
       o.order_date,
       o.status
FROM orders o
JOIN users u ON o.user_id = u.user_id;
```

**📖 Giải thích từng phần:**
```
FROM orders o         → Lấy dữ liệu từ bảng orders, đặt bí danh là "o"
JOIN users u          → Kết nối với bảng users, đặt bí danh là "u"
ON o.user_id = u.user_id  → Điều kiện nối: khớp user_id ở 2 bảng

u.full_name   → Lấy cột full_name từ bảng users (u)
o.order_id    → Lấy cột order_id từ bảng orders (o)
```

**📊 Kết quả trả về (một phần):**
```
full_name         | order_id | order_date  | status
------------------|----------|-------------|----------
Nguyễn Văn An    | 1        | 2024-01-10  | completed
Nguyễn Văn An    | 2        | 2024-01-15  | completed
Trần Thị Bình    | 3        | 2024-01-20  | completed
Trần Thị Bình    | 4        | 2024-02-01  | cancelled
... (20 dòng)
```

**⚠️ Lưu ý quan trọng:**
```
Tại sao cần viết u.full_name thay vì chỉ full_name?
→ Vì khi JOIN nhiều bảng, cần chỉ rõ cột thuộc bảng nào
→ Tránh lỗi "ambiguous column name" khi 2 bảng có tên cột trùng nhau
```

---

### Bài 7: JOIN 3 Bảng — Đơn Hàng + Người Dùng + Sản Phẩm

**📋 Yêu cầu:**
> Hiển thị danh sách đơn hàng đầy đủ thông tin gồm: **tên người dùng**, **tên sản phẩm**, số lượng, tổng tiền, ngày đặt.

**💡 Gợi ý:**
- Cần JOIN 3 bảng lần lượt
- `orders` → JOIN `users` (qua user_id) → JOIN `products` (qua product_id)

**🎯 Kết quả mong đợi:**
```
Hiển thị 20 dòng với đầy đủ tên người + tên sản phẩm + chi tiết đơn
```

---

**✅ Đáp án:**

```sql
SELECT u.full_name,
       p.product_name,
       o.quantity,
       o.total_price,
       o.order_date
FROM orders o
JOIN users    u ON o.user_id    = u.user_id
JOIN products p ON o.product_id = p.product_id;
```

**📖 Giải thích:**
```
FROM orders o                         → Bảng trung tâm là orders
JOIN users u    ON o.user_id = u.user_id      → Nối với users
JOIN products p ON o.product_id = p.product_id → Nối tiếp với products

Luồng dữ liệu:
orders ──(user_id)──► users
orders ──(product_id)──► products
```

**📊 Kết quả trả về (một phần):**
```
full_name         | product_name   | quantity | total_price | order_date
------------------|----------------|----------|-------------|------------
Nguyễn Văn An    | iPhone 15      | 1        | 25000000    | 2024-01-10
Nguyễn Văn An    | AirPods Pro    | 2        | 12000000    | 2024-01-15
Trần Thị Bình    | MacBook Pro    | 1        | 45000000    | 2024-01-20
Trần Thị Bình    | Samsung Buds   | 1        | 3500000     | 2024-02-01
... (20 dòng)
```

---

### Bài 8: JOIN + WHERE — Lọc Sau Khi Kết Hợp Bảng

**📋 Yêu cầu:**
> Tìm tất cả đơn hàng có trạng thái `'completed'`. Hiển thị: tên người dùng, tên sản phẩm, tổng tiền.

**💡 Gợi ý:**
- Làm tương tự bài 7 nhưng thêm `WHERE` để lọc theo `o.status`

**🎯 Kết quả mong đợi:**
```
Hiển thị 13 đơn hàng đã completed
```

---

**✅ Đáp án:**

```sql
SELECT u.full_name,
       p.product_name,
       o.total_price
FROM orders o
JOIN users    u ON o.user_id    = u.user_id
JOIN products p ON o.product_id = p.product_id
WHERE o.status = 'completed';
```

**📖 Giải thích:**
```
Cấu trúc SQL luôn theo thứ tự:
1. FROM   → Bảng nào
2. JOIN   → Nối với bảng nào
3. WHERE  → Lọc điều kiện
4. SELECT → Hiển thị cột nào

WHERE o.status = 'completed'
→ Chỉ lấy các đơn hàng đã hoàn thành
```

**📊 Kết quả trả về (một phần):**
```
full_name         | product_name   | total_price
------------------|----------------|------------
Nguyễn Văn An    | iPhone 15      | 25000000
Nguyễn Văn An    | AirPods Pro    | 12000000
Trần Thị Bình    | MacBook Pro    | 45000000
Lê Văn Cường     | Samsung S23    | 20000000
... (13 dòng)
```

---

### Bài 9: LEFT JOIN — Tìm Người Dùng Chưa Có Đơn Hàng

**📋 Yêu cầu:**
> Tìm những người dùng **chưa đặt đơn hàng nào**. Hiển thị tên và email của họ.

**💡 Gợi ý:**
- `INNER JOIN` (JOIN thông thường): chỉ lấy dữ liệu **khớp ở cả hai bảng**
- `LEFT JOIN`: lấy **tất cả** dữ liệu bảng trái, bảng phải không có thì để `NULL`
- Sau LEFT JOIN, dùng `WHERE o.order_id IS NULL` để tìm người chưa có đơn

**🎯 Kết quả mong đợi:**
```
Không ai trong dữ liệu mẫu hoàn toàn chưa có đơn
(Hoặc kết quả rỗng — điều này cũng là kết quả hợp lệ!)
```

---

**✅ Đáp án:**

```sql
SELECT u.full_name,
       u.email
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
WHERE o.order_id IS NULL;
```

**📖 Giải thích LEFT JOIN:**
```
INNER JOIN:                    LEFT JOIN:
users   orders                 users   orders
  1  ─── 1     →  hiện         1  ─── 1     → hiện
  2  ─── 2     →  hiện         2  ─── 2     → hiện
  X  (không có) → ẩN           X  (không có)→ hiện (orders = NULL)

WHERE o.order_id IS NULL
→ Chỉ lấy các user không có đơn hàng nào
```

**📊 Kết quả trả về:**
```
Kết quả rỗng (0 dòng)
→ Vì tất cả 10 user đều đã có ít nhất 1 đơn hàng trong dữ liệu mẫu
→ Đây là kết quả ĐÚNG — Tester cần hiểu "không có kết quả" cũng là valid!
```

**🔁 Thêm dữ liệu để test:**
```sql
-- Thêm user mới chưa có đơn hàng
INSERT INTO users VALUES
(11, 'Test User Mới', 'test@email.com', 20, 'Hà Nội', 'active', '2024-05-01');

-- Chạy lại query → sẽ thấy Test User Mới xuất hiện
```

---

### Bài 10: COUNT — Đếm Số Đơn Hàng Của Mỗi Người Dùng

**📋 Yêu cầu:**
> Đếm xem mỗi người dùng đã đặt **bao nhiêu đơn hàng**. Hiển thị tên người dùng và số đơn hàng. Sắp xếp theo số đơn hàng từ nhiều đến ít.

**💡 Gợi ý:**
- Dùng `COUNT()` để đếm
- Dùng `GROUP BY` để nhóm theo từng người dùng
- Dùng `ORDER BY ... DESC` để sắp xếp giảm dần

**🎯 Kết quả mong đợi:**
```
Hiển thị 10 dòng, người nhiều đơn nhất ở trên
```

---

**✅ Đáp án:**

```sql
SELECT u.full_name,
       COUNT(o.order_id) AS so_don_hang
FROM users u
JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.full_name
ORDER BY so_don_hang DESC;
```

**📖 Giải thích:**
```
COUNT(o.order_id)          → Đếm số đơn hàng của mỗi nhóm
AS so_don_hang             → Đặt tên hiển thị cho cột đếm
GROUP BY u.user_id, u.full_name → Nhóm theo từng người dùng
ORDER BY so_don_hang DESC  → Sắp xếp: nhiều nhất → ít nhất
```

**📊 Kết quả trả về:**
```
full_name         | so_don_hang
------------------|------------
Nguyễn Văn An    | 3
Trần Thị Bình    | 3
Phạm Thị Dung    | 2
Hoàng Văn Em     | 2
Đặng Văn Giang   | 2
Bùi Thị Hoa      | 2
Ngô Văn Inh      | 2
Lê Văn Cường     | 2
Vũ Thị Fang      | 1
Đinh Thị Kiều    | 1
```

---

## 🔴 MỨC NÂNG CAO (Bài 11–15): GROUP BY, HAVING và Tổng Hợp

---

### Bài 11: GROUP BY — Thống Kê Theo Danh Mục Sản Phẩm

**📋 Yêu cầu:**
> Thống kê mỗi **danh mục sản phẩm** có bao nhiêu sản phẩm và giá trung bình là bao nhiêu.

**💡 Gợi ý:**
- Dùng `GROUP BY category` để nhóm theo danh mục
- Dùng `COUNT(*)` để đếm
- Dùng `AVG(price)` để tính trung bình
- Dùng `ROUND()` để làm tròn số

**🎯 Kết quả mong đợi:**
```
Hiển thị 5 danh mục với số lượng và giá trung bình
```

---

**✅ Đáp án:**

```sql
SELECT category,
       COUNT(*)           AS so_san_pham,
       ROUND(AVG(price))  AS gia_trung_binh
FROM products
GROUP BY category
ORDER BY gia_trung_binh DESC;
```

**📖 Giải thích:**
```
GROUP BY category    → Gom tất cả sản phẩm cùng danh mục vào 1 nhóm
COUNT(*)             → Đếm có bao nhiêu sản phẩm trong nhóm đó
AVG(price)           → Tính giá trung bình trong nhóm đó
ROUND()              → Làm tròn số thập phân

Lưu ý: Khi dùng GROUP BY, các cột trong SELECT
phải là: cột GROUP BY hoặc hàm tổng hợp (COUNT, SUM, AVG, MIN, MAX)
```

**📊 Kết quả trả về:**
```
category        | so_san_pham | gia_trung_binh
----------------|-------------|----------------
Laptop          | 2           | 38500000
Máy tính bảng  | 1           | 18000000
Điện thoại      | 2           | 22500000
Màn hình        | 1           | 8000000
Phụ kiện        | 4           | 3250000
```

---

### Bài 12: SUM + GROUP BY — Doanh Thu Theo Tháng

**📋 Yêu cầu:**
> Tính **tổng doanh thu** của các đơn hàng có trạng thái `'completed'` theo từng tháng. Sắp xếp theo tháng tăng dần.

**💡 Gợi ý:**
- Dùng `SUM(total_price)` để tính tổng tiền
- Dùng hàm xử lý ngày để lấy tháng: `MONTH(order_date)` hoặc `EXTRACT(MONTH FROM order_date)`
- Lọc `WHERE status = 'completed'` trước khi tính

**🎯 Kết quả mong đợi:**
```
Hiển thị doanh thu theo từng tháng (tháng 1 đến tháng 4)
```

---

**✅ Đáp án:**

```sql
-- Cú pháp MySQL
SELECT MONTH(order_date)    AS thang,
       SUM(total_price)      AS doanh_thu
FROM orders
WHERE status = 'completed'
GROUP BY MONTH(order_date)
ORDER BY thang ASC;

-- Cú pháp PostgreSQL
SELECT EXTRACT(MONTH FROM order_date) AS thang,
       SUM(total_price)               AS doanh_thu
FROM orders
WHERE status = 'completed'
GROUP BY EXTRACT(MONTH FROM order_date)
ORDER BY thang ASC;
```

**📖 Giải thích:**
```
WHERE status = 'completed'   → Lọc TRƯỚC khi nhóm
GROUP BY MONTH(order_date)   → Nhóm theo tháng
SUM(total_price)             → Cộng tổng tiền trong từng tháng

Thứ tự xử lý SQL:
FROM → WHERE → GROUP BY → SELECT → ORDER BY
```

**📊 Kết quả trả về:**
```
thang | doanh_thu
------|----------
1     | 82000000    (Jan: đơn 1+2+3)
2     | 81000000    (Feb: đơn 5+6+8)
3     | 56500000    (Mar: đơn 10+11+13+18)
4     | 116000000   (Apr: đơn 15+16+17+20)
```

---

### Bài 13: HAVING — Lọc Sau Khi Nhóm

**📋 Yêu cầu:**
> Tìm những người dùng đã đặt **từ 2 đơn hàng trở lên**. Hiển thị tên và số đơn hàng.

**💡 Gợi ý:**
- `WHERE` dùng để lọc **trước** khi nhóm (lọc từng dòng)
- `HAVING` dùng để lọc **sau** khi đã nhóm (lọc từng nhóm)
- Không thể dùng `WHERE COUNT(...) > 2` — phải dùng `HAVING`

**🎯 Kết quả mong đợi:**
```
Hiển thị 8 người dùng có từ 2 đơn trở lên
```

---

**✅ Đáp án:**

```sql
SELECT u.full_name,
       COUNT(o.order_id) AS so_don_hang
FROM users u
JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.full_name
HAVING COUNT(o.order_id) >= 2
ORDER BY so_don_hang DESC;
```

**📖 Giải thích sự khác nhau WHERE vs HAVING:**
```
┌────────────────────────────────────────────────────┐
│  WHERE  → Lọc từng DÒNG (trước GROUP BY)           │
│  WHERE age > 25  → bỏ các dòng có age ≤ 25         │
├────────────────────────────────────────────────────┤
│  HAVING → Lọc từng NHÓM (sau GROUP BY)             │
│  HAVING COUNT(*) >= 2 → bỏ nhóm có ít hơn 2 dòng  │
└────────────────────────────────────────────────────┘

❌ SAI:  WHERE COUNT(order_id) >= 2   ← không được dùng
✅ ĐÚNG: HAVING COUNT(order_id) >= 2  ← phải dùng thế này
```

**📊 Kết quả trả về:**
```
full_name         | so_don_hang
------------------|------------
Nguyễn Văn An    | 3
Trần Thị Bình    | 3
Phạm Thị Dung    | 2
Hoàng Văn Em     | 2
Đặng Văn Giang   | 2
Bùi Thị Hoa      | 2
Ngô Văn Inh      | 2
Lê Văn Cường     | 2
```

---

### Bài 14: Tổng Hợp — Báo Cáo Đơn Hàng Theo Trạng Thái

**📋 Yêu cầu:**
> Tạo báo cáo thống kê đơn hàng theo trạng thái gồm: trạng thái, số đơn hàng, tổng tiền, tiền trung bình mỗi đơn. Sắp xếp theo tổng tiền giảm dần.

**💡 Gợi ý:**
- Dùng nhiều hàm tổng hợp trong cùng một câu query
- `COUNT`, `SUM`, `AVG` đều có thể dùng cùng lúc
- `ROUND(giá_trị, số_chữ_số_thập_phân)`

**🎯 Kết quả mong đợi:**
```
Hiển thị 3 dòng: completed, pending, cancelled với đủ số liệu
```

---

**✅ Đáp án:**

```sql
SELECT status                        AS trang_thai,
       COUNT(order_id)               AS so_don_hang,
       SUM(total_price)              AS tong_tien,
       ROUND(AVG(total_price), 0)    AS tien_tb_moi_don
FROM orders
GROUP BY status
ORDER BY tong_tien DESC;
```

**📊 Kết quả trả về:**
```
trang_thai | so_don_hang | tong_tien   | tien_tb_moi_don
-----------|-------------|-------------|----------------
completed  | 13          | 335500000   | 25808000
pending    | 5           | 115000000   | 23000000
cancelled  | 2           | 5500000     | 2750000
```

**📖 Phân tích kết quả (kỹ năng Tester):**
```
✓ Tổng đơn hàng: 13 + 5 + 2 = 20 ✅ (khớp với dữ liệu mẫu)
✓ completed chiếm 13/20 = 65% ✅
✓ cancelled chỉ 2 đơn với tiền thấp → hàng rẻ bị hủy nhiều hơn?
→ Đây là loại insight mà Tester nên chú ý khi kiểm tra data
```

---

### Bài 15: Bài Tổng Hợp — Dashboard Người Dùng VIP

**📋 Yêu cầu:**
> Tìm **top khách hàng** có tổng chi tiêu cao nhất. Hiển thị: tên, email, số đơn completed, tổng tiền đã chi, sản phẩm đắt nhất đã mua. Chỉ lấy khách có **ít nhất 2 đơn completed**.

**💡 Gợi ý:**
- Kết hợp JOIN 3 bảng
- Lọc chỉ lấy đơn `completed` với `WHERE`
- Dùng `GROUP BY` theo user
- Dùng `HAVING` để lọc từ 2 đơn trở lên
- Dùng `MAX(total_price)` cho đơn đắt nhất
- `ORDER BY` tổng tiền giảm dần

**🎯 Kết quả mong đợi:**
```
Top khách hàng VIP với ít nhất 2 đơn hoàn thành
```

---

**✅ Đáp án:**

```sql
SELECT u.full_name                   AS ten_khach,
       u.email                       AS email,
       COUNT(o.order_id)             AS so_don_completed,
       SUM(o.total_price)            AS tong_chi_tieu,
       MAX(o.total_price)            AS don_dat_nhat
FROM users u
JOIN orders o ON u.user_id = o.user_id
WHERE o.status = 'completed'
GROUP BY u.user_id, u.full_name, u.email
HAVING COUNT(o.order_id) >= 2
ORDER BY tong_chi_tieu DESC;
```

**📊 Kết quả trả về:**
```
ten_khach         | email                 | so_don | tong_chi_tieu | don_dat_nhat
------------------|-----------------------|--------|---------------|-------------
Trần Thị Bình    | binh.tran@email.com   | 3      | 104000000     | 45000000
Nguyễn Văn An    | an.nguyen@email.com   | 3      | 69000000      | 32000000
Phạm Thị Dung    | dung.pham@email.com   | 2      | 44000000      | 36000000
Bùi Thị Hoa      | hoa.bui@email.com     | 2      | 45000000      | 45000000
Ngô Văn Inh      | inh.ngo@email.com     | 2      | 38000000      | 20000000
Lê Văn Cường     | cuong.le@email.com    | 2      | 22000000      | 20000000
```

**📖 Tổng kết cấu trúc câu query hoàn chỉnh:**
```sql
SELECT   [cột cần hiển thị]        -- Bước 5: Hiển thị gì
FROM     [bảng chính]              -- Bước 1: Từ đâu
JOIN     [bảng phụ] ON [điều kiện]-- Bước 2: Kết hợp bảng
WHERE    [điều kiện lọc dòng]      -- Bước 3: Lọc trước nhóm
GROUP BY [cột nhóm]                -- Bước 4: Nhóm dữ liệu
HAVING   [điều kiện lọc nhóm]      -- Bước 4.5: Lọc sau nhóm
ORDER BY [cột sắp xếp]             -- Bước 6: Sắp xếp
LIMIT    [số dòng tối đa];         -- Bước 7: Giới hạn kết quả
```

---

# PHẦN 3: BẢNG TỔNG KẾT VÀ GHI NHỚ

## 3.1 Tóm Tắt 15 Bài Tập

| # | Chủ đề | Lệnh chính | Độ khó |
|---|--------|------------|--------|
| 1 | Lấy toàn bộ dữ liệu | `SELECT *` | 🟢 |
| 2 | Chọn cột cụ thể | `SELECT cột1, cột2` | 🟢 |
| 3 | Lọc đơn giản | `WHERE =` | 🟢 |
| 4 | Nhiều điều kiện | `WHERE AND / OR` | 🟢 |
| 5 | Lọc khoảng số | `BETWEEN` | 🟢 |
| 6 | Kết nối 2 bảng | `JOIN ... ON` | 🟡 |
| 7 | Kết nối 3 bảng | `JOIN + JOIN` | 🟡 |
| 8 | JOIN + lọc | `JOIN + WHERE` | 🟡 |
| 9 | Tìm bản ghi không tồn tại | `LEFT JOIN + IS NULL` | 🟡 |
| 10 | Đếm theo nhóm | `COUNT + GROUP BY` | 🟡 |
| 11 | Thống kê nhiều hàm | `COUNT + AVG + GROUP BY` | 🔴 |
| 12 | Tính tổng theo thời gian | `SUM + MONTH + GROUP BY` | 🔴 |
| 13 | Lọc nhóm | `GROUP BY + HAVING` | 🔴 |
| 14 | Báo cáo đa chiều | `COUNT + SUM + AVG` | 🔴 |
| 15 | Query tổng hợp | Tất cả kết hợp | 🔴 |

---

## 3.2 Các Lỗi Thường Gặp Khi Mới Học

```sql
-- ❌ LỖI 1: Quên dấu nháy đơn với chuỗi
WHERE status = active       -- SAI
WHERE status = 'active'     -- ĐÚNG

-- ❌ LỖI 2: Dùng WHERE thay vì HAVING sau GROUP BY
GROUP BY city
WHERE COUNT(*) > 2          -- SAI
HAVING COUNT(*) > 2         -- ĐÚNG

-- ❌ LỖI 3: SELECT cột không có trong GROUP BY
SELECT full_name, city, COUNT(*)
GROUP BY full_name           -- SAI: thiếu city
GROUP BY full_name, city     -- ĐÚNG

-- ❌ LỖI 4: Quên điều kiện ON khi JOIN
FROM orders JOIN users       -- SAI: thiếu ON
FROM orders JOIN users ON orders.user_id = users.user_id  -- ĐÚNG

-- ❌ LỖI 5: So sánh NULL sai cách
WHERE email = NULL           -- SAI
WHERE email IS NULL          -- ĐÚNG
```

---

## 3.3 Checklist Kiểm Tra Kết Quả Cho Tester

```
Sau mỗi câu query, tự hỏi:

□ Số dòng trả về có đúng không? (đếm thủ công để verify)
□ Các cột hiển thị có đúng tên không?
□ Dữ liệu có bị thiếu hoặc thừa không?
□ Số liệu tính toán (SUM, COUNT, AVG) có chính xác không?
□ Thứ tự sắp xếp có đúng yêu cầu không?
□ Kết quả rỗng (0 dòng) — có phải kết quả đúng không?
□ Có NULL nào không mong muốn không?
```

---

## 3.4 Công Thức SQL Hay Dùng Nhất

```sql
-- 🔧 Đếm tổng số bản ghi
SELECT COUNT(*) FROM ten_bang;

-- 🔧 Tìm giá trị lớn nhất / nhỏ nhất
SELECT MAX(price), MIN(price) FROM products;

-- 🔧 Lọc chuỗi chứa từ khóa
SELECT * FROM products WHERE product_name LIKE '%Pro%';

-- 🔧 Lọc trong danh sách
SELECT * FROM users WHERE city IN ('Hà Nội', 'HCM');

-- 🔧 Lọc giá trị NULL
SELECT * FROM users WHERE email IS NOT NULL;

-- 🔧 Giới hạn số dòng hiển thị
SELECT * FROM orders ORDER BY order_date DESC LIMIT 5;

-- 🔧 Đổi tên cột hiển thị
SELECT full_name AS "Họ tên", age AS "Tuổi" FROM users;
```

---

*Hoàn thành 15 bài tập trên, bạn đã nắm vững nền tảng SQL đủ dùng cho công việc Testing hàng ngày!*