Skip to content

📘 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ẽ:

  1. Viết SELECT query với WHERE, ORDER BY, LIMIT để lọc dữ liệu từ database
  2. Hiểu và sử dụng JOIN (INNER, LEFT, RIGHT, FULL) để kết nối bảng
  3. Dùng GROUP BY + aggregate functions (COUNT, SUM, AVG, MIN, MAX) để tổng hợp dữ liệu
  4. 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ệmEnglishMô tảVí dụ
BảngTableTập hợp dữ liệu cùng loại, gồm hàng và cộtBảng customers, orders, products
HàngRow / RecordMột bản ghi dữ liệu — tương tự 1 dòng trong Excel1 khách hàng, 1 đơn hàng, 1 sản phẩm
CộtColumn / FieldMột thuộc tính dữ liệu — tương tự 1 cột trong Excelcustomer_name, order_date, price
Khóa chínhPrimary Key (PK)Cột định danh duy nhất cho mỗi hàng — không trùng, không NULLcustomer_id, order_id, product_id
Khóa ngoạiForeign 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ảngorders.customer_idcustomers.customer_id
Kiểu dữ liệuData TypeLoại dữ liệu cho mỗi cộtINT, VARCHAR, DATE, DECIMAL, BOOLEAN
Lược đồSchemaCấ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ểuMô tảVí dụTương đương Excel
INT / INTEGERSố nguyên100, 2026, -5Number (0 decimals)
DECIMAL(p,s) / NUMERICSố thập phân chính xác199.99, 1500000.50Number (2 decimals)
VARCHAR(n)Chuỗi ký tự, tối đa n ký tự'Nguyễn Văn An', 'Hà Nội'Text
DATENgày tháng (YYYY-MM-DD)'2026-01-15'Date
TIMESTAMPNgày giờ chi tiết'2026-01-15 14:30:00'Date + Time
BOOLEANĐúng/SaiTRUE, FALSETRUE/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 B1 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 gian1 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 B1 customer có 1 profile (ít gặp hơn)

RDBMS phổ biến — so sánh:

RDBMSĐặc điểmKhi nào dùngChi phí
PostgreSQLOpen-source, mạnh, chuẩn SQLProduction database, analyticsMiễn phí
MySQLOpen-source, phổ biến, nhanhWeb application, startupMiễn phí
SQLiteFile-based, không cần serverHọc tập, prototype, mobile appMiễn phí
BigQueryCloud data warehouse (Google)Phân tích data lớn, BIPay-per-query
SQL ServerEnterprise (Microsoft)Doanh nghiệp, kết hợp .NETTrả 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:

OperatorMô tảVí dụ
=BằngWHERE city = 'Hà Nội'
<> hoặc !=KhácWHERE status <> 'cancelled'
>, <, >=, <=So sánhWHERE total_amount >= 1000000
BETWEEN...ANDTrong khoảng (bao gồm 2 đầu)WHERE order_date BETWEEN '2026-01-01' AND '2026-01-31'
IN (...)Thuộc danh sáchWHERE city IN ('Hà Nội', 'HCM', 'Đà Nẵng')
LIKETìm kiếm pattern (% = bất kỳ, _ = 1 ký tự)WHERE product_name LIKE '%Áo%'
IS NULLGiá trị NULL (trống)WHERE email IS NULL
IS NOT NULLKhông NULLWHERE phone IS NOT NULL
ANDKết hợp nhiều điều kiện (tất cả phải đúng)WHERE city = 'HCM' AND total_amount > 500000
ORMột trong các điều kiện đúngWHERE city = 'Hà Nội' OR city = 'Đà Nẵng'
NOTPhủ địnhWHERE 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àmMô tảVí dụ
IS NULLKiểm tra NULLWHERE email IS NULL — tìm khách hàng chưa có email
IS NOT NULLKiểm tra không NULLWHERE 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ênCOALESCE(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 = bNULLIF(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 JOINMô tảKết quảKhi nào dùng
INNER JOINChỉ lấy dòng khớp cả 2 bảngGiao của 2 bảngKhi chỉ cần dữ liệu có đầy đủ ở cả 2 bảng
LEFT JOINLấ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 giaoKhi 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 JOINLấ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 JOINLấy tất cả cả 2 bảng (NULL nếu không khớp)Hợp của 2 bảngKhi 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 + B

Cú 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ạiMô tảVí dụ
SELF JOINBảng tự JOIN với chính nóTìm nhân viên và manager trong cùng bảng employees
CROSS JOINKế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 MATCHSQL JOIN
Tốc độChậm khi > 100K dòngNhanh trên triệu dòng
Số bảngThường 2 (chính + phụ)Không giới hạn
Hướng tra cứuVLOOKUP chỉ trái → phảiBất kỳ hướng nào
Xử lý không khớp#N/A → cần IFERRORNULL tự động (LEFT JOIN)
Tái sử dụngCopy paste công thứcLư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_idcustomer_namecity
1Nguyễn Văn AnHà Nội
2Trần Thị BìnhHCM
3Lê Văn CườngĐà Nẵng
4Phạm Thị DungHải Phòng

Bảng orders:

order_idcustomer_idorder_datetotal_amount
10112026-01-15500000
10222026-01-161200000
10312026-01-20300000
10452026-01-22750000

Kết quả các loại JOIN:

JOIN typeKết quảGhi chú
INNER JOIN3 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 JOIN6 dòngTấ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àmMô tảVí dụTương đương Excel
COUNT(*)Đếm số dòngCOUNT(*) — đếm tất cả đơn hàngCOUNTA
COUNT(column)Đếm giá trị không NULLCOUNT(email) — đếm KH có emailCOUNTA (tự bỏ qua blank)
COUNT(DISTINCT col)Đếm giá trị duy nhấtCOUNT(DISTINCT customer_id) — đếm KH uniqueKhông có (cần helper column)
SUM(column)TổngSUM(total_amount) — tổng doanh thuSUM
AVG(column)Trung bìnhAVG(total_amount) — doanh thu TB/đơnAVERAGE
MIN(column)Giá trị nhỏ nhấtMIN(order_date) — ngày đặt hàng đầu tiênMIN
MAX(column)Giá trị lớn nhấtMAX(total_amount) — đơn hàng lớn nhấtMAX

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íWHEREHAVING
Thời điểm lọcTrước GROUP BY — lọc từng dòngSau 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ộiHAVING 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ánGROUP BYAggregateJOIN
Doanh thu theo thành phốcitySUM(total_amount)orders ↔ customers
Số đơn hàng theo sản phẩmproduct_nameCOUNT(*)order_items ↔ products
Doanh thu TB theo segment KHsegmentAVG(total_amount)orders ↔ customers
Top 5 sản phẩm bán chạyproduct_nameSUM(quantity)order_items ↔ products
Khách hàng mua > 3 đơncustomer_idCOUNT(*) + HAVINGorders ↔ 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ếtCâu lệnhThứ tự thực thiMô tả
1SELECT5Chọn cột & tính toán
2FROM1Xác định bảng nguồn
3JOIN ... ON2Kết nối bảng
4WHERE3Lọc dòng (trước nhóm)
5GROUP BY4Nhóm dữ liệu
6HAVING6Lọc nhóm (sau nhóm)
7ORDER BY7Sắp xếp kết quả
8LIMIT8Giớ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ướcHành độngCâu hỏi cần trả lời
1Xác định bảng chính (fact table)Bảng nào chứa sự kiện kinh doanh? (orders, transactions)
2Xác định bảng phụ (dimension table)Bảng nào chứa thông tin mô tả? (customers, products, regions)
3Tìm Primary Key mỗi bảngCột nào định danh duy nhất mỗi dòng?
4Tìm Foreign Key & quan hệBảng nào liên kết với bảng nào, qua cột nào?
5Xác định cardinalityQuan hệ 1-1, 1-N hay N-N?

SQL vs Excel — Bảng so sánh tổng hợp

Thao tácExcelSQL
Lọc dữ liệuFilter, AutoFilterWHERE
Sắp xếpSort A→Z / Z→AORDER BY ASC/DESC
Giới hạn hiển thịẨn dòng / scrollLIMIT
Tra cứu liên bảngVLOOKUP, INDEX MATCHJOIN
Tổng hợp theo nhómPivot TableGROUP BY + aggregates
Đếm có điều kiệnCOUNTIFSCOUNT(*) + WHERE/HAVING
Tổng có điều kiệnSUMIFSSUM() + WHERE
Loại trùngRemove DuplicatesDISTINCT
Xử lý trốngIFERROR, 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ánSQL conceptMô tả
Số chuyến xe theo thành phố/ngàyGROUP BY city, DATE(trip_date) + COUNT(*)Dashboard vận hành: theo dõi volume realtime
Doanh thu theo driver segmentJOIN drivers + GROUP BY segment + SUM(fare)So sánh hiệu quả: UberX vs UberXL vs UberBlack
Tỷ lệ hủy chuyếnCOUNT(CASE WHEN status='cancelled') / COUNT(*)KPI chất lượng dịch vụ — target < 5%
Thời gian chờ trung bìnhAVG(pickup_time - request_time)Trải nghiệm khách hàng — target < 5 phút
Top 10 tuyến đường phổ biếnGROUP BY pickup_zone, dropoff_zone + ORDER BY COUNT(*) DESC LIMIT 10Planning: bố trí xe ở đâu cho hiệu quả
Surge pricing analysisWHERE surge_multiplier > 1 + GROUP BY hourPhâ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 — WHERE trước GROUP BY giú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ảngMô tảDòng (ước tính)
ordersĐơn hàng50M+
order_itemsChi tiết sản phẩm trong đơn120M+
customersKhách hàng15M+
productsSản phẩm5M+
sellersNgười bán100K+
categoriesDanh mục sản phẩm10K+
reviewsĐánh giá sản phẩm30M+

Các bài toán SQL hàng ngày tại Tiki:

Bài toánStakeholderSQL approach
GMV (Gross Merchandise Value) theo ngàyCEO, CFOSUM(order_value) GROUP BY DATE(order_date)
Tỷ lệ hoàn trả theo categoryCategory ManagerCOUNT(CASE WHEN status='returned') / COUNT(*) GROUP BY category
Top seller theo doanh thuSeller OperationsJOIN sellers + GROUP BY seller_id + ORDER BY SUM DESC LIMIT 20
Khách hàng mới vs quay lạiMarketingMIN(order_date) per customer → so sánh first order date vs current month
Đánh giá trung bình theo sản phẩmProduct teamAVG(rating) GROUP BY product_id HAVING COUNT(review_id) >= 10
Conversion rate theo nguồn trafficDigital MarketingJOIN 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 BigQueryRedshift — 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 ... WHERE vớ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 BY kết hợp aggregate functions
  • [ ] Tôi phân biệt được WHERE (trước GROUP BY) vs HAVING (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ệtEnglishGiải thích
Ngôn ngữ truy vấnSQL (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ệuQueryCâ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 DatabaseHệ thống lưu trữ dữ liệu theo bảng, liên kết qua key
Khóa chínhPrimary 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ạiForeign 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ảngJOINThao tác ghép dữ liệu từ 2+ bảng dựa trên cột chung
Kết nối trongINNER JOINChỉ lấy dòng khớp ở cả 2 bảng
Kết nối tráiLEFT JOINLấ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ệuAggregationQuá trình nhóm dữ liệu và tính giá trị tổng hợp (COUNT, SUM, AVG...)
Nhóm dữ liệuGROUP BYMệnh đề SQL để nhóm dòng có giá trị giống nhau → tính aggregate
Lọc sau nhómHAVINGLọc kết quả sau GROUP BY — dùng được với aggregate functions
Loại trùng lặpDISTINCTLoại bỏ giá trị trùng lặp trong kết quả query
Truy vấn lồng nhauSubqueryCâ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