Appearance
📘 Buổi 5: SQL cơ bản — Query dữ liệu từ database
Nếu chỉ được học 1 công cụ DA, đó phải là SQL. — Ngôn ngữ chung của Data
🎯 Mục tiêu buổi học
Sau buổi này, học viên sẽ:
- Viết SELECT query với WHERE, ORDER BY, LIMIT để lọc dữ liệu từ database
- Hiểu và sử dụng JOIN (INNER, LEFT, RIGHT, FULL) để kết nối bảng
- Dùng GROUP BY + aggregate functions (COUNT, SUM, AVG, MIN, MAX) để tổng hợp dữ liệu
- Hiểu cấu trúc relational database: table, primary key, foreign key
📋 Tổng quan
Ở Buổi 3–4, bạn đã thành thạo Excel — từ Data Cleaning, Pivot Table đến Dashboard và Sales Analysis. Excel mạnh, nhưng có giới hạn: file nặng khi vượt 100.000 dòng, khó quản lý nhiều bảng liên kết, không hỗ trợ nhiều người truy cập đồng thời. Trong thực tế doanh nghiệp, dữ liệu bán hàng, khách hàng, sản phẩm — tất cả nằm trong database, và ngôn ngữ để "nói chuyện" với database là SQL (Structured Query Language).
SQL là kỹ năng bắt buộc #1 của mọi Data Analyst. Theo khảo sát của DataCamp (2025), SQL xuất hiện trong 65% job description cho vị trí DA — cao hơn cả Python (55%) và Excel (50%). LinkedIn cũng xếp SQL là kỹ năng được nhà tuyển dụng yêu cầu nhiều nhất trong lĩnh vực data. Lý do đơn giản: dù bạn dùng Python, R, Power BI hay Tableau — dữ liệu đều phải được query từ database trước khi phân tích, và SQL là cách nhanh nhất, trực tiếp nhất để lấy đúng dữ liệu bạn cần.
Còn nhớ ở Buổi 4, bạn dùng VLOOKUP/INDEX MATCH để liên kết bảng đơn hàng với bảng sản phẩm trong Excel? SQL làm điều tương tự — nhưng mạnh hơn, nhanh hơn, và xử lý được hàng triệu dòng chỉ trong vài giây. Khái niệm JOIN trong SQL chính là phiên bản "nâng cấp" của VLOOKUP mà bạn đã quen. Và GROUP BY + aggregate functions chính là Pivot Table — nhưng viết bằng code, tái sử dụng được, và chạy trên bất kỳ lượng dữ liệu nào.
Buổi học hôm nay đưa bạn sang giai đoạn mới trong hành trình DA: từ spreadsheet sang database — từ click chuột sang viết query — từ file Excel sang hệ thống dữ liệu chuyên nghiệp. Đây là bước Prepare + Process trong Google Data Analytics Framework mà bạn đã học ở Buổi 1, nhưng ở quy mô lớn hơn nhiều.
📌 Phần 1: Relational Database cơ bản
Khái niệm
Relational Database (cơ sở dữ liệu quan hệ) là hệ thống lưu trữ dữ liệu theo bảng (table), trong đó các bảng được liên kết với nhau thông qua các trường chung (key). Đây là mô hình database phổ biến nhất thế giới, được phát minh bởi Edgar F. Codd tại IBM năm 1970 và vẫn thống trị đến ngày nay.
Các khái niệm cốt lõi:
| Khái niệm | English | Mô tả | Ví dụ |
|---|---|---|---|
| Bảng | Table | Tập hợp dữ liệu cùng loại, gồm hàng và cột | Bảng customers, orders, products |
| Hàng | Row / Record | Một bản ghi dữ liệu — tương tự 1 dòng trong Excel | 1 khách hàng, 1 đơn hàng, 1 sản phẩm |
| Cột | Column / Field | Một thuộc tính dữ liệu — tương tự 1 cột trong Excel | customer_name, order_date, price |
| Khóa chính | Primary Key (PK) | Cột định danh duy nhất cho mỗi hàng — không trùng, không NULL | customer_id, order_id, product_id |
| Khóa ngoại | Foreign Key (FK) | Cột tham chiếu đến Primary Key của bảng khác — tạo liên kết giữa 2 bảng | orders.customer_id → customers.customer_id |
| Kiểu dữ liệu | Data Type | Loại dữ liệu cho mỗi cột | INT, VARCHAR, DATE, DECIMAL, BOOLEAN |
| Lược đồ | Schema | Cấu trúc tổng thể: danh sách bảng, cột, kiểu dữ liệu, quan hệ | ERD (Entity Relationship Diagram) |
Kiểu dữ liệu phổ biến trong SQL:
| Kiểu | Mô tả | Ví dụ | Tương đương Excel |
|---|---|---|---|
INT / INTEGER | Số nguyên | 100, 2026, -5 | Number (0 decimals) |
DECIMAL(p,s) / NUMERIC | Số thập phân chính xác | 199.99, 1500000.50 | Number (2 decimals) |
VARCHAR(n) | Chuỗi ký tự, tối đa n ký tự | 'Nguyễn Văn An', 'Hà Nội' | Text |
DATE | Ngày tháng (YYYY-MM-DD) | '2026-01-15' | Date |
TIMESTAMP | Ngày giờ chi tiết | '2026-01-15 14:30:00' | Date + Time |
BOOLEAN | Đúng/Sai | TRUE, FALSE | TRUE/FALSE |
Tại sao quan trọng cho Data Analyst?
Trong doanh nghiệp, dữ liệu không bao giờ nằm trong 1 file Excel. Hệ thống ERP, CRM, e-commerce platform — tất cả đều lưu trữ dữ liệu trong relational database. Hiểu cấu trúc database giúp bạn:
- Biết dữ liệu nằm ở đâu — bảng nào chứa thông tin gì
- Hiểu quan hệ giữa các bảng — customer liên kết với order như thế nào, order chứa product nào
- Viết query chính xác — tránh sai sót khi JOIN, tránh duplicate khi kết nối bảng
Nhớ lại Buổi 4, khi bạn dùng VLOOKUP để tra cứu tên sản phẩm từ bảng Product Master? Đó chính là thao tác "liên kết 2 bảng qua key chung" — và trong database, nó được gọi là JOIN. Hiểu Primary Key và Foreign Key là nền tảng để viết JOIN đúng.
Áp dụng thực tế
ERD (Entity Relationship Diagram) — Database e-commerce mẫu:
┌──────────────────┐ ┌──────────────────────┐ ┌──────────────────┐
│ customers │ │ orders │ │ products │
├──────────────────┤ ├──────────────────────┤ ├──────────────────┤
│ customer_id (PK) │───┐ │ order_id (PK) │ ┌───│ product_id (PK) │
│ customer_name │ └──>│ customer_id (FK) │ │ │ product_name │
│ email │ │ order_date │ │ │ category │
│ city │ │ total_amount │ │ │ price │
│ segment │ │ status │ │ │ stock_quantity │
└──────────────────┘ └──────────────────────┘ │ └──────────────────┘
│
┌──────────────────────┐ │
│ order_items │ │
├──────────────────────┤ │
│ item_id (PK) │ │
│ order_id (FK) │ │
│ product_id (FK) ────┘ │
│ quantity │
│ unit_price │
└──────────────────────┘Quan hệ giữa các bảng:
| Quan hệ | Mô tả | Ví dụ |
|---|---|---|
| 1 — N (One-to-Many) | 1 bản ghi bảng A liên kết với nhiều bản ghi bảng B | 1 customer có nhiều orders |
| N — N (Many-to-Many) | Nhiều A liên kết với nhiều B — cần bảng trung gian | 1 order có nhiều products, 1 product xuất hiện trong nhiều orders → bảng order_items |
| 1 — 1 (One-to-One) | 1 A liên kết với đúng 1 B | 1 customer có 1 profile (ít gặp hơn) |
RDBMS phổ biến — so sánh:
| RDBMS | Đặc điểm | Khi nào dùng | Chi phí |
|---|---|---|---|
| PostgreSQL | Open-source, mạnh, chuẩn SQL | Production database, analytics | Miễn phí |
| MySQL | Open-source, phổ biến, nhanh | Web application, startup | Miễn phí |
| SQLite | File-based, không cần server | Học tập, prototype, mobile app | Miễn phí |
| BigQuery | Cloud data warehouse (Google) | Phân tích data lớn, BI | Pay-per-query |
| SQL Server | Enterprise (Microsoft) | Doanh nghiệp, kết hợp .NET | Trả phí |
📌 Phần 2: SELECT & Filtering — Truy vấn và lọc dữ liệu
Khái niệm
SELECT là câu lệnh cơ bản nhất và quan trọng nhất trong SQL — dùng để lấy dữ liệu từ bảng. Kết hợp với WHERE (lọc điều kiện), ORDER BY (sắp xếp), LIMIT (giới hạn số dòng) — bạn có thể trích xuất chính xác dữ liệu cần phân tích chỉ trong 1 câu query.
Cú pháp cơ bản:
sql
SELECT cột_1, cột_2, ... -- Chọn cột cần lấy
FROM tên_bảng -- Từ bảng nào
WHERE điều_kiện -- Lọc theo điều kiện (tùy chọn)
ORDER BY cột_sắp_xếp -- Sắp xếp kết quả (tùy chọn)
LIMIT số_dòng; -- Giới hạn số dòng trả về (tùy chọn)Bảng tổng hợp operators trong WHERE:
| Operator | Mô tả | Ví dụ |
|---|---|---|
= | Bằng | WHERE city = 'Hà Nội' |
<> hoặc != | Khác | WHERE status <> 'cancelled' |
>, <, >=, <= | So sánh | WHERE total_amount >= 1000000 |
BETWEEN...AND | Trong khoảng (bao gồm 2 đầu) | WHERE order_date BETWEEN '2026-01-01' AND '2026-01-31' |
IN (...) | Thuộc danh sách | WHERE city IN ('Hà Nội', 'HCM', 'Đà Nẵng') |
LIKE | Tìm kiếm pattern (% = bất kỳ, _ = 1 ký tự) | WHERE product_name LIKE '%Áo%' |
IS NULL | Giá trị NULL (trống) | WHERE email IS NULL |
IS NOT NULL | Không NULL | WHERE phone IS NOT NULL |
AND | Kết hợp nhiều điều kiện (tất cả phải đúng) | WHERE city = 'HCM' AND total_amount > 500000 |
OR | Một trong các điều kiện đúng | WHERE city = 'Hà Nội' OR city = 'Đà Nẵng' |
NOT | Phủ định | WHERE NOT city = 'Hà Nội' |
Tại sao quan trọng cho Data Analyst?
SELECT là câu query bạn viết nhiều nhất trong sự nghiệp DA — chiếm 80%+ thời gian làm việc với SQL. Mỗi khi sếp hỏi "Cho anh/chị danh sách khách hàng VIP tháng này" hay "Bao nhiêu đơn hàng từ Hà Nội trong Q1?" — câu trả lời bắt đầu bằng SELECT.
Nhớ lại Buổi 3: bạn dùng Filter + Sort trong Excel để lọc và sắp xếp dữ liệu. WHERE chính là Filter, ORDER BY chính là Sort — nhưng mạnh hơn: bạn có thể kết hợp nhiều điều kiện phức tạp, tìm kiếm pattern với LIKE, xử lý NULL — những thứ Excel phải dùng nhiều hàm phụ mới làm được.
Áp dụng thực tế
NULL Handling — xử lý giá trị trống:
| Hàm | Mô tả | Ví dụ |
|---|---|---|
IS NULL | Kiểm tra NULL | WHERE email IS NULL — tìm khách hàng chưa có email |
IS NOT NULL | Kiểm tra không NULL | WHERE phone IS NOT NULL — chỉ lấy khách có SĐT |
COALESCE(a, b, c) | Trả về giá trị không NULL đầu tiên | COALESCE(phone, email, 'N/A') — ưu tiên SĐT, rồi email, cuối cùng 'N/A' |
NULLIF(a, b) | Trả về NULL nếu a = b | NULLIF(discount, 0) — tránh chia cho 0 |
Lưu ý quan trọng: NULL không phải là 0, không phải là chuỗi rỗng ''. NULL nghĩa là "không biết" — và mọi phép toán với NULL đều trả về NULL. Không thể dùng = NULL mà phải dùng IS NULL.
📌 Phần 3: JOIN — Kết nối bảng
Khái niệm
JOIN là thao tác kết hợp dữ liệu từ 2 hoặc nhiều bảng dựa trên cột chung (thường là Primary Key — Foreign Key). Đây là sức mạnh cốt lõi của relational database: dữ liệu được tách thành nhiều bảng nhỏ (normalization) → khi cần phân tích, dùng JOIN để ghép lại.
4 loại JOIN chính:
| Loại JOIN | Mô tả | Kết quả | Khi nào dùng |
|---|---|---|---|
| INNER JOIN | Chỉ lấy dòng khớp cả 2 bảng | Giao của 2 bảng | Khi chỉ cần dữ liệu có đầy đủ ở cả 2 bảng |
| LEFT JOIN | Lấy tất cả bảng trái + khớp bảng phải (NULL nếu không khớp) | Toàn bộ bảng trái + phần giao | Khi cần giữ tất cả dòng bảng chính, kể cả không có dữ liệu liên kết |
| RIGHT JOIN | Lấy tất cả bảng phải + khớp bảng trái (NULL nếu không khớp) | Toàn bộ bảng phải + phần giao | Ít dùng — thường đổi thành LEFT JOIN cho dễ đọc |
| FULL OUTER JOIN | Lấy tất cả cả 2 bảng (NULL nếu không khớp) | Hợp của 2 bảng | Khi cần so sánh, đối soát 2 nguồn dữ liệu |
Minh họa trực quan JOIN (Venn Diagram):
INNER JOIN: LEFT JOIN: RIGHT JOIN: FULL OUTER JOIN:
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│ A │▓▓│ B │ │▓A▓│▓▓│ B │ │ A │▓▓│▓B▓│ │▓A▓│▓▓│▓B▓│
└───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘
Chỉ phần chung Toàn bộ A Toàn bộ B Toàn bộ A + BCú pháp:
sql
-- INNER JOIN
SELECT a.cột_1, b.cột_2
FROM bảng_A a
INNER JOIN bảng_B b ON a.key = b.key;
-- LEFT JOIN
SELECT a.cột_1, b.cột_2
FROM bảng_A a
LEFT JOIN bảng_B b ON a.key = b.key;Các loại JOIN khác (giới thiệu):
| Loại | Mô tả | Ví dụ |
|---|---|---|
| SELF JOIN | Bảng tự JOIN với chính nó | Tìm nhân viên và manager trong cùng bảng employees |
| CROSS JOIN | Kết hợp mỗi dòng bảng A với mỗi dòng bảng B (tích Descartes) | Tạo tổ hợp tất cả sản phẩm × tất cả khu vực |
Tại sao quan trọng cho Data Analyst?
JOIN là kỹ năng quan trọng thứ 2 sau SELECT — vì dữ liệu trong database luôn được phân tách (normalized) thành nhiều bảng. Bạn không thể phân tích đơn hàng mà không JOIN với bảng khách hàng (để biết ai mua) và bảng sản phẩm (để biết mua gì).
Nhớ lại Buổi 4: VLOOKUP/INDEX MATCH trong Excel chính là phiên bản "thủ công" của LEFT JOIN — bạn tra cứu từ bảng phụ sang bảng chính. Trong SQL, JOIN làm điều này tự động, nhanh hơn, và xử lý được hàng triệu dòng.
So sánh Excel Lookup vs SQL JOIN:
| Tiêu chí | Excel VLOOKUP/INDEX MATCH | SQL JOIN |
|---|---|---|
| Tốc độ | Chậm khi > 100K dòng | Nhanh trên triệu dòng |
| Số bảng | Thường 2 (chính + phụ) | Không giới hạn |
| Hướng tra cứu | VLOOKUP chỉ trái → phải | Bất kỳ hướng nào |
| Xử lý không khớp | #N/A → cần IFERROR | NULL tự động (LEFT JOIN) |
| Tái sử dụng | Copy paste công thức | Lưu query, chạy lại bất kỳ lúc nào |
Áp dụng thực tế
Ví dụ minh họa với dữ liệu mẫu:
Bảng customers:
| customer_id | customer_name | city |
|---|---|---|
| 1 | Nguyễn Văn An | Hà Nội |
| 2 | Trần Thị Bình | HCM |
| 3 | Lê Văn Cường | Đà Nẵng |
| 4 | Phạm Thị Dung | Hải Phòng |
Bảng orders:
| order_id | customer_id | order_date | total_amount |
|---|---|---|---|
| 101 | 1 | 2026-01-15 | 500000 |
| 102 | 2 | 2026-01-16 | 1200000 |
| 103 | 1 | 2026-01-20 | 300000 |
| 104 | 5 | 2026-01-22 | 750000 |
Kết quả các loại JOIN:
| JOIN type | Kết quả | Ghi chú |
|---|---|---|
| INNER JOIN | 3 dòng (order 101, 102, 103) | Customer_id = 5 không có trong customers → bị loại; Customer 3, 4 không có order → bị loại |
| LEFT JOIN (orders LEFT JOIN customers) | 4 dòng (tất cả orders) | Order 104 (customer_id=5) → customer_name = NULL |
| LEFT JOIN (customers LEFT JOIN orders) | 5 dòng (tất cả customers) | Customer 3, 4 → order columns = NULL (chưa mua hàng) |
| FULL OUTER JOIN | 6 dòng | Tất cả cả 2 bảng — NULL ở phía thiếu |
📌 Phần 4: Aggregation — Tổng hợp dữ liệu
Khái niệm
Aggregation (tổng hợp) là quá trình nhóm dữ liệu và tính toán giá trị tổng hợp — tương tự Pivot Table trong Excel. SQL sử dụng GROUP BY kết hợp với các hàm tổng hợp (aggregate functions) để thực hiện điều này.
Aggregate functions cơ bản:
| Hàm | Mô tả | Ví dụ | Tương đương Excel |
|---|---|---|---|
COUNT(*) | Đếm số dòng | COUNT(*) — đếm tất cả đơn hàng | COUNTA |
COUNT(column) | Đếm giá trị không NULL | COUNT(email) — đếm KH có email | COUNTA (tự bỏ qua blank) |
COUNT(DISTINCT col) | Đếm giá trị duy nhất | COUNT(DISTINCT customer_id) — đếm KH unique | Không có (cần helper column) |
SUM(column) | Tổng | SUM(total_amount) — tổng doanh thu | SUM |
AVG(column) | Trung bình | AVG(total_amount) — doanh thu TB/đơn | AVERAGE |
MIN(column) | Giá trị nhỏ nhất | MIN(order_date) — ngày đặt hàng đầu tiên | MIN |
MAX(column) | Giá trị lớn nhất | MAX(total_amount) — đơn hàng lớn nhất | MAX |
Cú pháp GROUP BY:
sql
SELECT cột_nhóm, aggregate_function(cột_tính)
FROM tên_bảng
WHERE điều_kiện_trước_nhóm -- Lọc TRƯỚC khi nhóm
GROUP BY cột_nhóm -- Nhóm dữ liệu
HAVING điều_kiện_sau_nhóm -- Lọc SAU khi nhóm
ORDER BY cột_sắp_xếp; -- Sắp xếp kết quảWHERE vs HAVING — khác biệt quan trọng:
| Tiêu chí | WHERE | HAVING |
|---|---|---|
| Thời điểm lọc | Trước GROUP BY — lọc từng dòng | Sau GROUP BY — lọc kết quả nhóm |
| Dùng aggregate | ❌ Không thể | ✅ Có thể |
| Ví dụ | WHERE city = 'Hà Nội' — chỉ tính đơn Hà Nội | HAVING COUNT(*) >= 5 — chỉ lấy nhóm có ≥ 5 đơn |
DISTINCT — loại bỏ trùng lặp:
sql
-- Đếm bao nhiêu thành phố có đơn hàng
SELECT COUNT(DISTINCT city) AS so_thanh_pho
FROM customers;
-- Danh sách category không trùng
SELECT DISTINCT category
FROM products;Tại sao quan trọng cho Data Analyst?
Aggregation là trái tim của mọi báo cáo. Khi sếp hỏi "Doanh thu tháng 1 bao nhiêu?", "Khu vực nào bán nhiều nhất?", "Khách hàng nào mua nhiều nhất?" — câu trả lời luôn cần GROUP BY + aggregate functions. Đây chính là Descriptive Analytics mà bạn đã học ở Buổi 1 — nhưng giờ thực hiện trên database chuyên nghiệp thay vì Excel.
Nhớ lại Pivot Table ở Buổi 3: bạn kéo Khu vực vào Rows, Doanh thu vào Values (SUM). Trong SQL, câu tương đương là:
sql
-- Pivot Table "Doanh thu theo khu vực" bằng SQL
SELECT city, SUM(total_amount) AS tong_doanh_thu
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
GROUP BY city
ORDER BY tong_doanh_thu DESC;Áp dụng thực tế
Subquery cơ bản — query lồng trong query:
sql
-- Tìm khách hàng có tổng mua hàng > trung bình
SELECT customer_name, tong_mua
FROM (
-- Subquery: tính tổng mua cho mỗi khách hàng
SELECT c.customer_name, SUM(o.total_amount) AS tong_mua
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_name
) AS customer_summary
WHERE tong_mua > (SELECT AVG(total_amount) FROM orders);Bảng tổng hợp — kết hợp GROUP BY + JOIN + aggregate:
| Bài toán | GROUP BY | Aggregate | JOIN |
|---|---|---|---|
| Doanh thu theo thành phố | city | SUM(total_amount) | orders ↔ customers |
| Số đơn hàng theo sản phẩm | product_name | COUNT(*) | order_items ↔ products |
| Doanh thu TB theo segment KH | segment | AVG(total_amount) | orders ↔ customers |
| Top 5 sản phẩm bán chạy | product_name | SUM(quantity) | order_items ↔ products |
| Khách hàng mua > 3 đơn | customer_id | COUNT(*) + HAVING | orders ↔ customers |
📊 Framework tổng hợp
SQL Query Execution Order — Thứ tự thực thi câu SQL
Thứ tự bạn viết SQL khác với thứ tự SQL thực thi. Hiểu thứ tự thực thi giúp bạn debug query hiệu quả:
| Thứ tự viết | Câu lệnh | Thứ tự thực thi | Mô tả |
|---|---|---|---|
| 1 | SELECT | 5 | Chọn cột & tính toán |
| 2 | FROM | 1 | Xác định bảng nguồn |
| 3 | JOIN ... ON | 2 | Kết nối bảng |
| 4 | WHERE | 3 | Lọc dòng (trước nhóm) |
| 5 | GROUP BY | 4 | Nhóm dữ liệu |
| 6 | HAVING | 6 | Lọc nhóm (sau nhóm) |
| 7 | ORDER BY | 7 | Sắp xếp kết quả |
| 8 | LIMIT | 8 | Giới hạn số dòng |
Cách nhớ thứ tự thực thi: From → Join → Where → Group by → Having → Select → Order by → Limit → viết tắt: "FJ-WG-HS-OL"
THỨ TỰ VIẾT THỨ TỰ THỰC THI
┌─────────────────┐ ┌─────────────────┐
│ 1. SELECT │ │ 1. FROM │
│ 2. FROM │ │ 2. JOIN...ON │
│ 3. JOIN...ON │ ≠ │ 3. WHERE │
│ 4. WHERE │ │ 4. GROUP BY │
│ 5. GROUP BY │ │ 5. SELECT │
│ 6. HAVING │ │ 6. HAVING │
│ 7. ORDER BY │ │ 7. ORDER BY │
│ 8. LIMIT │ │ 8. LIMIT │
└─────────────────┘ └─────────────────┘ERD Reading Guide — Cách đọc sơ đồ database
Khi bắt đầu dự án phân tích mới, bước đầu tiên là đọc ERD để hiểu cấu trúc dữ liệu:
| Bước | Hành động | Câu hỏi cần trả lời |
|---|---|---|
| 1 | Xác định bảng chính (fact table) | Bảng nào chứa sự kiện kinh doanh? (orders, transactions) |
| 2 | Xác định bảng phụ (dimension table) | Bảng nào chứa thông tin mô tả? (customers, products, regions) |
| 3 | Tìm Primary Key mỗi bảng | Cột nào định danh duy nhất mỗi dòng? |
| 4 | Tìm Foreign Key & quan hệ | Bảng nào liên kết với bảng nào, qua cột nào? |
| 5 | Xác định cardinality | Quan hệ 1-1, 1-N hay N-N? |
SQL vs Excel — Bảng so sánh tổng hợp
| Thao tác | Excel | SQL |
|---|---|---|
| Lọc dữ liệu | Filter, AutoFilter | WHERE |
| Sắp xếp | Sort A→Z / Z→A | ORDER BY ASC/DESC |
| Giới hạn hiển thị | Ẩn dòng / scroll | LIMIT |
| Tra cứu liên bảng | VLOOKUP, INDEX MATCH | JOIN |
| Tổng hợp theo nhóm | Pivot Table | GROUP BY + aggregates |
| Đếm có điều kiện | COUNTIFS | COUNT(*) + WHERE/HAVING |
| Tổng có điều kiện | SUMIFS | SUM() + WHERE |
| Loại trùng | Remove Duplicates | DISTINCT |
| Xử lý trống | IFERROR, IF(ISBLANK()) | COALESCE, IS NULL |
🛠️ Demo thực hành: E-commerce Database
Setup — Tạo database mẫu
sql
-- =============================================
-- DATABASE E-COMMERCE: 4 bảng chính
-- Dùng cho tất cả ví dụ trong buổi học
-- =============================================
-- Bảng 1: Khách hàng
CREATE TABLE customers (
customer_id INT PRIMARY KEY,
customer_name VARCHAR(100),
email VARCHAR(100),
city VARCHAR(50),
segment VARCHAR(20) -- 'VIP', 'Standard', 'New'
);
INSERT INTO customers VALUES
(1, 'Nguyễn Văn An', 'an@email.com', 'Hà Nội', 'VIP'),
(2, 'Trần Thị Bình', 'binh@email.com', 'HCM', 'Standard'),
(3, 'Lê Văn Cường', NULL, 'Đà Nẵng', 'VIP'),
(4, 'Phạm Thị Dung', 'dung@email.com', 'Hà Nội', 'New'),
(5, 'Hoàng Văn Em', 'em@email.com', 'HCM', 'Standard'),
(6, 'Ngô Thị Fương', NULL, 'Đà Nẵng', 'New'),
(7, 'Đỗ Văn Giang', 'giang@email.com', 'Hải Phòng', 'VIP');
-- Bảng 2: Sản phẩm
CREATE TABLE products (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
category VARCHAR(50),
price DECIMAL(12,2),
stock_quantity INT
);
INSERT INTO products VALUES
(101, 'Áo Thun Basic', 'Áo', 250000, 500),
(102, 'Quần Jean Slim', 'Quần', 450000, 300),
(103, 'Áo Khoác Gió', 'Áo', 350000, 200),
(104, 'Giày Thể Thao', 'Giày', 800000, 150),
(105, 'Balo Laptop', 'Phụ kiện', 600000, 100),
(106, 'Mũ Lưỡi Trai', 'Phụ kiện', 150000, 400),
(107, 'Váy Midi', 'Váy', 380000, 250);
-- Bảng 3: Đơn hàng
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
total_amount DECIMAL(12,2),
status VARCHAR(20) -- 'completed', 'pending', 'cancelled'
);
INSERT INTO orders VALUES
(1001, 1, '2026-01-05', 750000, 'completed'),
(1002, 2, '2026-01-07', 450000, 'completed'),
(1003, 1, '2026-01-10', 1200000, 'completed'),
(1004, 3, '2026-01-12', 800000, 'completed'),
(1005, 4, '2026-01-15', 500000, 'pending'),
(1006, 5, '2026-01-18', 950000, 'completed'),
(1007, 2, '2026-01-20', 380000, 'completed'),
(1008, 1, '2026-01-22', 600000, 'completed'),
(1009, 3, '2026-01-25', 1500000, 'completed'),
(1010, 6, '2026-01-28', 250000, 'cancelled'),
(1011, 5, '2026-02-01', 700000, 'completed'),
(1012, 7, '2026-02-03', 1800000, 'completed'),
(1013, 1, '2026-02-05', 350000, 'completed'),
(1014, 2, '2026-02-08', 600000, 'pending'),
(1015, 4, '2026-02-10', 450000, 'completed');
-- Bảng 4: Chi tiết đơn hàng
CREATE TABLE order_items (
item_id INT PRIMARY KEY,
order_id INT,
product_id INT,
quantity INT,
unit_price DECIMAL(12,2)
);
INSERT INTO order_items VALUES
(1, 1001, 101, 2, 250000),
(2, 1001, 106, 1, 150000),
(3, 1002, 102, 1, 450000),
(4, 1003, 104, 1, 800000),
(5, 1003, 103, 1, 350000),
(6, 1004, 104, 1, 800000),
(7, 1005, 101, 2, 250000),
(8, 1006, 102, 1, 450000),
(9, 1006, 101, 2, 250000),
(10, 1007, 107, 1, 380000),
(11, 1008, 105, 1, 600000),
(12, 1009, 104, 1, 800000),
(13, 1009, 103, 2, 350000),
(14, 1010, 106, 1, 150000),
(15, 1011, 102, 1, 450000),
(16, 1011, 106, 1, 150000),
(17, 1012, 104, 1, 800000),
(18, 1012, 105, 1, 600000),
(19, 1012, 103, 1, 350000),
(20, 1013, 101, 1, 250000),
(21, 1014, 105, 1, 600000),
(22, 1015, 102, 1, 450000);Query 1: SELECT cơ bản — Xem danh sách khách hàng
sql
-- Lấy tất cả khách hàng
SELECT *
FROM customers;
-- Chỉ lấy tên và thành phố
SELECT customer_name, city
FROM customers;Query 2: WHERE — Lọc đơn hàng đã hoàn thành
sql
-- Đơn hàng completed có giá trị > 500,000
SELECT order_id, customer_id, order_date, total_amount
FROM orders
WHERE status = 'completed'
AND total_amount > 500000
ORDER BY total_amount DESC;Query 3: BETWEEN + IN — Lọc theo khoảng và danh sách
sql
-- Đơn hàng trong tháng 1/2026, từ khách ở Hà Nội hoặc HCM
SELECT o.order_id, c.customer_name, c.city, o.order_date, o.total_amount
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_date BETWEEN '2026-01-01' AND '2026-01-31'
AND c.city IN ('Hà Nội', 'HCM')
ORDER BY o.order_date;Query 4: LIKE + NULL — Tìm kiếm pattern và xử lý NULL
sql
-- Tìm sản phẩm có tên chứa "Áo"
SELECT product_name, category, price
FROM products
WHERE product_name LIKE '%Áo%';
-- Tìm khách hàng chưa có email
SELECT customer_name, city,
COALESCE(email, 'Chưa cập nhật') AS email_status
FROM customers
WHERE email IS NULL;Query 5: INNER JOIN — Ghép đơn hàng với khách hàng
sql
-- Danh sách đơn hàng kèm tên khách hàng và thành phố
SELECT
o.order_id,
c.customer_name,
c.city,
o.order_date,
o.total_amount,
o.status
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id
ORDER BY o.order_date DESC;Query 6: LEFT JOIN — Tìm khách hàng chưa đặt đơn nào
sql
-- Tất cả khách hàng, kể cả chưa có đơn hàng
SELECT
c.customer_name,
c.city,
c.segment,
COUNT(o.order_id) AS so_don_hang,
COALESCE(SUM(o.total_amount), 0) AS tong_doanh_thu
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_name, c.city, c.segment
ORDER BY tong_doanh_thu DESC;Query 7: Multi-table JOIN — Ghép 3 bảng để phân tích chi tiết
sql
-- Chi tiết: Khách nào mua sản phẩm gì, số lượng bao nhiêu
SELECT
c.customer_name,
c.city,
p.product_name,
p.category,
oi.quantity,
oi.unit_price,
(oi.quantity * oi.unit_price) AS thanh_tien
FROM order_items oi
JOIN orders o ON oi.order_id = o.order_id
JOIN customers c ON o.customer_id = c.customer_id
JOIN products p ON oi.product_id = p.product_id
WHERE o.status = 'completed'
ORDER BY thanh_tien DESC;Query 8: GROUP BY + Aggregate — Doanh thu theo thành phố
sql
-- Doanh thu, số đơn, giá trị TB theo thành phố
SELECT
c.city AS thanh_pho,
COUNT(o.order_id) AS so_don_hang,
SUM(o.total_amount) AS tong_doanh_thu,
ROUND(AVG(o.total_amount), 0) AS gia_tri_tb,
MIN(o.total_amount) AS don_nho_nhat,
MAX(o.total_amount) AS don_lon_nhat
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.status = 'completed'
GROUP BY c.city
ORDER BY tong_doanh_thu DESC;Query 9: HAVING — Lọc khách hàng mua >= 2 đơn
sql
-- Khách hàng trung thành: mua từ 2 đơn trở lên
SELECT
c.customer_name,
c.segment,
COUNT(o.order_id) AS so_don_hang,
SUM(o.total_amount) AS tong_chi_tieu,
ROUND(AVG(o.total_amount), 0) AS chi_tieu_tb
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.status = 'completed'
GROUP BY c.customer_name, c.segment
HAVING COUNT(o.order_id) >= 2 -- Lọc SAU khi nhóm
ORDER BY tong_chi_tieu DESC;Query 10: Top sản phẩm bán chạy nhất
sql
-- Top 5 sản phẩm bán chạy nhất (theo số lượng)
SELECT
p.product_name,
p.category,
SUM(oi.quantity) AS tong_so_luong,
SUM(oi.quantity * oi.unit_price) AS tong_doanh_thu,
COUNT(DISTINCT o.order_id) AS so_don_hang
FROM order_items oi
JOIN products p ON oi.product_id = p.product_id
JOIN orders o ON oi.order_id = o.order_id
WHERE o.status = 'completed'
GROUP BY p.product_name, p.category
ORDER BY tong_so_luong DESC
LIMIT 5;Query 11: Doanh thu theo tháng — Time-series analysis
sql
-- Doanh thu theo tháng (chỉ đơn completed)
SELECT
DATE_TRUNC('month', o.order_date) AS thang,
COUNT(o.order_id) AS so_don_hang,
SUM(o.total_amount) AS doanh_thu,
COUNT(DISTINCT o.customer_id) AS so_khach_mua
FROM orders o
WHERE o.status = 'completed'
GROUP BY DATE_TRUNC('month', o.order_date)
ORDER BY thang;Query 12: Phân tích doanh thu theo segment khách hàng
sql
-- So sánh hiệu quả các segment khách hàng
SELECT
c.segment,
COUNT(DISTINCT c.customer_id) AS so_khach,
COUNT(o.order_id) AS so_don_hang,
SUM(o.total_amount) AS tong_doanh_thu,
ROUND(AVG(o.total_amount), 0) AS don_hang_tb,
ROUND(SUM(o.total_amount) * 100.0 /
(SELECT SUM(total_amount) FROM orders WHERE status = 'completed'), 1
) AS phan_tram_doanh_thu
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
AND o.status = 'completed'
GROUP BY c.segment
ORDER BY tong_doanh_thu DESC;Query 13: Doanh thu theo category sản phẩm
sql
-- Phân tích theo nhóm sản phẩm
SELECT
p.category,
COUNT(DISTINCT p.product_id) AS so_san_pham,
SUM(oi.quantity) AS tong_so_luong_ban,
SUM(oi.quantity * oi.unit_price) AS tong_doanh_thu,
ROUND(AVG(oi.unit_price), 0) AS gia_ban_tb
FROM order_items oi
JOIN products p ON oi.product_id = p.product_id
JOIN orders o ON oi.order_id = o.order_id
WHERE o.status = 'completed'
GROUP BY p.category
ORDER BY tong_doanh_thu DESC;🏪 Ví dụ thực tế
🌍 Quốc tế: Uber — SQL để phân tích hàng triệu chuyến xe mỗi ngày
Uber xử lý hơn 25 triệu chuyến xe mỗi ngày trên toàn cầu (tính đến 2026). Đội ngũ Data Analyst tại Uber sử dụng SQL (trên Hive/Presto/BigQuery) để phân tích mọi khía cạnh vận hành.
Các bài toán SQL điển hình tại Uber:
| Bài toán | SQL concept | Mô tả |
|---|---|---|
| Số chuyến xe theo thành phố/ngày | GROUP BY city, DATE(trip_date) + COUNT(*) | Dashboard vận hành: theo dõi volume realtime |
| Doanh thu theo driver segment | JOIN drivers + GROUP BY segment + SUM(fare) | So sánh hiệu quả: UberX vs UberXL vs UberBlack |
| Tỷ lệ hủy chuyến | COUNT(CASE WHEN status='cancelled') / COUNT(*) | KPI chất lượng dịch vụ — target < 5% |
| Thời gian chờ trung bình | AVG(pickup_time - request_time) | Trải nghiệm khách hàng — target < 5 phút |
| Top 10 tuyến đường phổ biến | GROUP BY pickup_zone, dropoff_zone + ORDER BY COUNT(*) DESC LIMIT 10 | Planning: bố trí xe ở đâu cho hiệu quả |
| Surge pricing analysis | WHERE surge_multiplier > 1 + GROUP BY hour | Phân tích giờ cao điểm → tối ưu pricing |
Ví dụ SQL tại Uber (đơn giản hóa):
sql
-- Doanh thu theo thành phố và loại xe trong tháng 1/2026
SELECT
t.city,
t.ride_type,
COUNT(*) AS so_chuyen,
SUM(t.fare) AS tong_doanh_thu,
ROUND(AVG(t.fare), 0) AS gia_cuoc_tb,
ROUND(AVG(t.trip_duration_min), 1) AS thoi_gian_tb_phut
FROM trips t
WHERE t.trip_date BETWEEN '2026-01-01' AND '2026-01-31'
AND t.status = 'completed'
GROUP BY t.city, t.ride_type
HAVING COUNT(*) >= 100 -- Chỉ thành phố có >= 100 chuyến
ORDER BY tong_doanh_thu DESC;Bài học cho DA:
- Uber chạy SQL trên petabytes data — cùng cú pháp SELECT/JOIN/GROUP BY bạn đang học
- SQL execution order rất quan trọng khi query chạy trên data lớn —
WHEREtrướcGROUP BYgiúp giảm data xử lý - Real-world database có hàng chục bảng liên kết — cần đọc ERD kỹ trước khi viết query
🇻🇳 Việt Nam: Tiki — SQL cho phân tích e-commerce
Tiki — sàn thương mại điện tử hàng đầu Việt Nam — quản lý hàng triệu sản phẩm, đơn hàng và khách hàng trong hệ thống database. Đội ngũ DA tại Tiki viết SQL hàng ngày để phục vụ báo cáo và phân tích.
Database schema tại Tiki (đơn giản hóa):
| Bảng | Mô tả | Dòng (ước tính) |
|---|---|---|
orders | Đơn hàng | 50M+ |
order_items | Chi tiết sản phẩm trong đơn | 120M+ |
customers | Khách hàng | 15M+ |
products | Sản phẩm | 5M+ |
sellers | Người bán | 100K+ |
categories | Danh mục sản phẩm | 10K+ |
reviews | Đánh giá sản phẩm | 30M+ |
Các bài toán SQL hàng ngày tại Tiki:
| Bài toán | Stakeholder | SQL approach |
|---|---|---|
| GMV (Gross Merchandise Value) theo ngày | CEO, CFO | SUM(order_value) GROUP BY DATE(order_date) |
| Tỷ lệ hoàn trả theo category | Category Manager | COUNT(CASE WHEN status='returned') / COUNT(*) GROUP BY category |
| Top seller theo doanh thu | Seller Operations | JOIN sellers + GROUP BY seller_id + ORDER BY SUM DESC LIMIT 20 |
| Khách hàng mới vs quay lại | Marketing | MIN(order_date) per customer → so sánh first order date vs current month |
| Đánh giá trung bình theo sản phẩm | Product team | AVG(rating) GROUP BY product_id HAVING COUNT(review_id) >= 10 |
| Conversion rate theo nguồn traffic | Digital Marketing | JOIN sessions + orders → COUNT(orders)/COUNT(sessions) GROUP BY utm_source |
Ví dụ SQL tại Tiki (đơn giản hóa):
sql
-- Top 10 category theo doanh thu Q1/2026
SELECT
c.category_name,
COUNT(DISTINCT o.order_id) AS so_don_hang,
SUM(oi.quantity * oi.unit_price) AS gmv,
COUNT(DISTINCT o.customer_id) AS so_khach_mua,
ROUND(AVG(r.rating), 1) AS rating_tb
FROM order_items oi
JOIN orders o ON oi.order_id = o.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN categories c ON p.category_id = c.category_id
LEFT JOIN reviews r ON p.product_id = r.product_id
WHERE o.order_date BETWEEN '2026-01-01' AND '2026-03-31'
AND o.status = 'completed'
GROUP BY c.category_name
ORDER BY gmv DESC
LIMIT 10;Bài học cho DA:
- Tiki dùng BigQuery và Redshift — cloud data warehouse, cùng cú pháp SQL chuẩn
- Query thực tế thường JOIN 4–6 bảng — hiểu ERD là bắt buộc
- LEFT JOIN dùng nhiều hơn INNER JOIN vì cần giữ lại cả dòng không có dữ liệu liên kết (khách chưa review, sản phẩm chưa bán)
- Luôn thêm
WHERE status = 'completed'— loại đơn hủy, đơn trả để tính doanh thu chính xác
✅ Checklist buổi học
Sau buổi học này, bạn nên tự tin trả lời "Có" cho tất cả các câu hỏi dưới đây:
- [ ] Tôi giải thích được relational database là gì: table, row, column, data type
- [ ] Tôi phân biệt được Primary Key vs Foreign Key và hiểu vai trò của chúng
- [ ] Tôi đọc được ERD (Entity Relationship Diagram) cơ bản
- [ ] Tôi viết được
SELECT ... FROM ... WHEREvới các operators:=,<>,>,<,BETWEEN,IN,LIKE - [ ] Tôi sử dụng được
ORDER BY(ASC/DESC) vàLIMITđể sắp xếp và giới hạn kết quả - [ ] Tôi xử lý được NULL:
IS NULL,IS NOT NULL,COALESCE - [ ] Tôi phân biệt được 4 loại JOIN: INNER, LEFT, RIGHT, FULL OUTER
- [ ] Tôi viết được multi-table JOIN (3+ bảng) với đúng điều kiện ON
- [ ] Tôi biết khi nào dùng INNER JOIN vs LEFT JOIN trong thực tế
- [ ] Tôi sử dụng thành thạo aggregate functions:
COUNT,SUM,AVG,MIN,MAX - [ ] Tôi viết được
GROUP BYkết hợp aggregate functions - [ ] Tôi phân biệt được
WHERE(trước GROUP BY) vsHAVING(sau GROUP BY) - [ ] Tôi dùng được
COUNT(DISTINCT ...)để đếm giá trị duy nhất - [ ] Tôi hiểu SQL execution order: FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
- [ ] Tôi viết được subquery cơ bản (query lồng trong query)
🔑 Từ khóa quan trọng
| Tiếng Việt | English | Giải thích |
|---|---|---|
| Ngôn ngữ truy vấn | SQL (Structured Query Language) | Ngôn ngữ chuẩn để truy vấn, quản lý dữ liệu trong relational database |
| Truy vấn dữ liệu | Query | Câu lệnh SQL để lấy, lọc, tổng hợp dữ liệu từ database |
| Cơ sở dữ liệu quan hệ | Relational Database | Hệ thống lưu trữ dữ liệu theo bảng, liên kết qua key |
| Khóa chính | Primary Key (PK) | Cột định danh duy nhất cho mỗi hàng trong bảng — không trùng, không NULL |
| Khóa ngoại | Foreign Key (FK) | Cột tham chiếu đến Primary Key của bảng khác — tạo liên kết giữa 2 bảng |
| Kết nối bảng | JOIN | Thao tác ghép dữ liệu từ 2+ bảng dựa trên cột chung |
| Kết nối trong | INNER JOIN | Chỉ lấy dòng khớp ở cả 2 bảng |
| Kết nối trái | LEFT JOIN | Lấy tất cả bảng trái + khớp bảng phải (NULL nếu không khớp) |
| Tổng hợp dữ liệu | Aggregation | Quá trình nhóm dữ liệu và tính giá trị tổng hợp (COUNT, SUM, AVG...) |
| Nhóm dữ liệu | GROUP BY | Mệnh đề SQL để nhóm dòng có giá trị giống nhau → tính aggregate |
| Lọc sau nhóm | HAVING | Lọc kết quả sau GROUP BY — dùng được với aggregate functions |
| Loại trùng lặp | DISTINCT | Loại bỏ giá trị trùng lặp trong kết quả query |
| Truy vấn lồng nhau | Subquery | Câu query nằm bên trong câu query khác — dùng làm bộ lọc hoặc bảng tạm |
| Sơ đồ quan hệ | ERD (Entity Relationship Diagram) | Biểu đồ mô tả cấu trúc database: bảng, cột, quan hệ giữa các bảng |
🔗 Xem thêm Buổi 5
→ 📝 Blog → 🧠 Case Study → 🏆 Tiêu chuẩn → 🛠 Workshop → 🎮 Mini Game