SQL 1 — Nhập môn SQL & mô hình quan hệ
Mở đầu: tại sao phải học SQL?
Hầu hết dữ liệu nghiệp vụ trên thế giới — giao dịch ngân hàng, đơn hàng thương mại điện tử, hồ sơ nhân sự — đều được lưu trong cơ sở dữ liệu quan hệ (relational database). Và ngôn ngữ chuẩn để nói chuyện với những cơ sở dữ liệu đó là SQL (Structured Query Language). Học tốt SQL nghĩa là bạn có thể tự mình trả lời câu hỏi "khách hàng nào ở Hà Nội có số dư tài khoản lớn nhất?" mà không cần chờ ai khác làm hộ.
Loạt bài "zero to hero" này sẽ đưa bạn từ con số 0 đến mức tự tin viết truy vấn phức tạp. Bài đầu tiên đặt nền móng: hiểu mô hình quan hệ, các kiểu dữ liệu, khái niệm khoá, và viết câu SELECT đầu tiên. Bạn nên đọc thật kỹ, vì mọi bài sau đều dựa trên các khái niệm ở đây.
Cơ sở dữ liệu quan hệ (RDBMS) là gì?
RDBMS (Relational Database Management System) là phần mềm quản lý dữ liệu được tổ chức thành các bảng (table) có quan hệ với nhau. Các hệ quản trị phổ biến gồm PostgreSQL, MySQL, SQL Server, Oracle và SQLite. Tất cả đều dùng SQL, dù mỗi hệ có một vài khác biệt nhỏ về cú pháp (gọi là "dialect").
Ý tưởng cốt lõi của mô hình quan hệ — do Edgar F. Codd đề xuất năm 1970 — rất đơn giản nhưng mạnh mẽ: mọi dữ liệu đều được biểu diễn dưới dạng bảng hai chiều gồm hàng và cột, và các bảng có thể liên kết với nhau thông qua các giá trị chung. Thay vì lập trình viên phải mô tả cách lấy dữ liệu, họ chỉ cần mô tả cái họ muốn, còn RDBMS lo phần tối ưu thực thi.
Bảng, hàng, cột
Ba khái niệm nền tảng:
- Bảng (table): một tập hợp dữ liệu về một loại thực thể, ví dụ bảng
customerschứa thông tin khách hàng. Bảng đôi khi còn gọi là relation. - Cột (column): một thuộc tính của thực thể, ví dụ
full_name,city. Mỗi cột có một kiểu dữ liệu cố định. Cột còn gọi là attribute hay field. - Hàng (row): một bản ghi cụ thể, ví dụ một khách hàng tên "Nguyễn Văn A" ở "Hà Nội". Hàng còn gọi là record hay tuple.
Hình dung bảng customers như một bảng tính:
| id | full_name | city | created_at |
|---|---|---|---|
| 1 | Nguyễn Văn A | Hà Nội | 2025-01-10 09:00:00 |
| 2 | Trần Thị B | Đà Nẵng | 2025-02-03 14:30:00 |
| 3 | Lê Văn C | Hà Nội | 2025-03-21 08:15:00 |
Mỗi dòng là một hàng, mỗi tiêu đề cột (id, full_name, ...) là một cột. Điểm khác biệt quan trọng so với bảng tính Excel: trong RDBMS, thứ tự các hàng không có ý nghĩa trừ khi bạn yêu cầu sắp xếp, và mọi giá trị trong cùng một cột phải cùng kiểu dữ liệu.
Khoá chính và khoá ngoại
Để các bảng liên kết được với nhau một cách đáng tin cậy, mô hình quan hệ dùng hai loại khoá.
Khoá chính (primary key)
Khoá chính là một (hoặc nhiều) cột định danh duy nhất mỗi hàng trong bảng. Trong bảng customers, cột id là khoá chính: không có hai khách hàng nào trùng id, và id không bao giờ rỗng. Khoá chính đảm bảo bạn luôn chỉ đích danh được một hàng cụ thể.
Đặc điểm của khoá chính:
- Duy nhất: không trùng lặp.
- Không NULL: bắt buộc phải có giá trị.
- Ổn định: lý tưởng là không bao giờ thay đổi sau khi gán.
Khoá ngoại (foreign key)
Khoá ngoại là cột trong một bảng tham chiếu tới khoá chính của bảng khác, tạo nên mối quan hệ. Trong sandbox của chúng ta:
accounts.customer_idlà khoá ngoại trỏ tớicustomers.id— mỗi tài khoản thuộc về một khách hàng.transactions.account_idlà khoá ngoại trỏ tớiaccounts.id— mỗi giao dịch thuộc về một tài khoản.
Khoá ngoại thực thi toàn vẹn tham chiếu (referential integrity): bạn không thể tạo một tài khoản với customer_id = 999 nếu không tồn tại khách hàng id = 999. Điều này ngăn dữ liệu "mồ côi" và giữ cho cơ sở dữ liệu luôn nhất quán.
Sơ đồ quan hệ customers — accounts — transactions
Mối quan hệ giữa ba bảng cốt lõi của sandbox như sau:
Đọc sơ đồ: một khách hàng (customers) có thể có không hoặc nhiều tài khoản (accounts) — ký hiệu ||--o{ nghĩa là quan hệ một–nhiều. Tương tự, một tài khoản có thể ghi nhận nhiều giao dịch (transactions). Đây là kiểu quan hệ phổ biến nhất bạn sẽ gặp khi làm việc với dữ liệu thực tế.
Các kiểu dữ liệu phổ biến
Mỗi cột được khai báo với một kiểu dữ liệu (data type) quyết định giá trị nào hợp lệ. Chọn đúng kiểu giúp tiết kiệm bộ nhớ, tránh lỗi và cho phép các phép tính chính xác. Những kiểu hay dùng nhất:
| Kiểu | Dùng cho | Ví dụ |
|---|---|---|
INT / INTEGER | Số nguyên (id, đếm, tuổi) | 42, -7 |
NUMERIC / DECIMAL | Số thập phân chính xác (tiền tệ, số dư) | 1500000.50 |
TEXT / VARCHAR(n) | Chuỗi ký tự (tên, địa chỉ) | 'Nguyễn Văn A' |
DATE | Ngày (không có giờ) | '2025-06-30' |
TIMESTAMP | Ngày kèm giờ phút giây | '2025-06-30 14:30:00' |
BOOLEAN | Giá trị đúng/sai | TRUE, FALSE |
Vài lưu ý quan trọng:
NUMERICcho tiền, không dùng số thực dấu phẩy động (FLOAT/REAL). Số dấu phẩy động lưu gần đúng và có thể gây sai lệch khi cộng dồn — điều cấm kỵ với tài chính. Trong sandbox, cộtbalancevàamountđều làNUMERIC.VARCHAR(n)giới hạn độ dài tối đanký tự;TEXTkhông giới hạn (tùy hệ quản trị). Với hầu hết nhu cầu hiện đại,TEXTlà lựa chọn an toàn.TIMESTAMPthường được dùng để ghi nhận thời điểm phát sinh sự kiện, ví dụcreated_atcủa khách hàng hay giao dịch.
NULL — giá trị "không biết"
NULL là một trạng thái đặc biệt nghĩa là không có giá trị / chưa biết, khác hẳn với 0 hay chuỗi rỗng ''. Ví dụ: một nhân viên chưa có ngày vào làm có thể có hired_at = NULL.
Điều khiến người mới bối rối nhất: NULL không bằng bất cứ thứ gì, kể cả chính nó. Phép so sánh NULL = NULL không trả về TRUE mà trả về NULL (được hiểu như "không xác định"). Vì vậy để kiểm tra một cột có rỗng hay không, bạn phải dùng IS NULL hoặc IS NOT NULL, không dùng = NULL:
-- ĐÚNG
SELECT * FROM employees WHERE hired_at IS NULL;
-- SAI (luôn trả về 0 hàng)
SELECT * FROM employees WHERE hired_at = NULL;
Chúng ta sẽ đào sâu về NULL ở các bài sau, nhưng hãy ghi nhớ nguyên tắc này ngay từ đầu.
SQL là ngôn ngữ khai báo
SQL là một ngôn ngữ khai báo (declarative language). Bạn mô tả kết quả mong muốn chứ không mô tả từng bước thực hiện như trong ngôn ngữ thủ tục (Python, Java...).
Ví dụ, khi bạn viết "lấy tất cả khách hàng ở Hà Nội", bạn không cần chỉ định cách RDBMS duyệt bảng, có dùng chỉ mục (index) hay không, đọc dữ liệu theo thứ tự nào. Bộ tối ưu hoá truy vấn (query optimizer) bên trong sẽ tự tìm cách thực thi hiệu quả nhất. Đây là điểm mạnh lớn: bạn tập trung vào "cái gì", máy lo "như thế nào".
SQL gồm nhiều nhóm lệnh, nhưng loạt bài này tập trung trước hết vào nhóm DQL (Data Query Language) — cụ thể là câu lệnh SELECT để đọc dữ liệu. Các nhóm khác như INSERT/UPDATE/DELETE (DML) và CREATE/ALTER (DDL) sẽ xuất hiện ở các bài sau.
Cấu trúc câu SELECT cơ bản
Câu lệnh SELECT là công cụ bạn dùng nhiều nhất. Dạng cơ bản nhất:
SELECT <danh sách cột>
FROM <tên bảng>
WHERE <điều kiện lọc>;
Diễn giải ba mệnh đề chính:
SELECT: chọn những cột nào sẽ hiển thị. Dùng*để lấy tất cả cột.FROM: chỉ định bảng nguồn lấy dữ liệu.WHERE: lọc, chỉ giữ những hàng thoả điều kiện. Mệnh đề này không bắt buộc.
Dấu chấm phẩy ; ở cuối đánh dấu kết thúc câu lệnh.
Trình tự xử lý logic
Một điều thú vị: dù bạn viết SELECT trước, RDBMS xử lý logic theo thứ tự khác — FROM trước (xác định bảng), rồi WHERE (lọc hàng), cuối cùng mới SELECT (chọn cột để trả về). Hiểu điều này giúp bạn lý giải vì sao một số cú pháp ở các bài sau lại có những ràng buộc nhất định.
Lý thuyết đủ rồi — hãy chạy thử!
Dưới đây là các ví dụ bạn có thể chạy ngay. Schema sandbox để học gồm các bảng:
departments(id, name)employees(id, name, department_id, salary, hired_at)customers(id, full_name, city, created_at)accounts(id, customer_id, account_no, balance, currency)transactions(id, account_id, amount, kind, created_at)— trong đókindnhận giá trị'credit'(ghi có) hoặc'debit'(ghi nợ).
Ví dụ 1: Lấy toàn bộ một bảng
Câu lệnh đơn giản nhất — xem tất cả khách hàng:
▶ Chạy được trong SQL Builder
SELECT * FROM customers;
* nghĩa là "mọi cột". Kết quả trả về tất cả các hàng và tất cả các cột của bảng customers. Đây là cách nhanh nhất để xem một bảng có gì, nhưng trong thực tế nên tránh SELECT * cho truy vấn chính thức vì nó lấy cả những cột bạn không cần.
Ví dụ 2: Chọn vài cột cụ thể
Thay vì lấy tất cả, chỉ lấy tên và thành phố của khách hàng:
▶ Chạy được trong SQL Builder
SELECT full_name, city
FROM customers;
Kết quả gọn gàng hơn, chỉ gồm hai cột. Thứ tự cột trong kết quả đúng theo thứ tự bạn liệt kê sau SELECT.
Ví dụ 3: Lọc dữ liệu bằng WHERE
Bây giờ thêm điều kiện — chỉ lấy khách hàng ở Hà Nội:
▶ Chạy được trong SQL Builder
SELECT full_name, city
FROM customers
WHERE city = 'Hà Nội';
Lưu ý chuỗi ký tự đặt trong dấu nháy đơn '...', không phải nháy kép. Phép so sánh = ở đây là so sánh bằng. RDBMS duyệt qua từng hàng, giữ lại những hàng có city đúng bằng 'Hà Nội'.
Ví dụ 4: Điều kiện trên số
WHERE không chỉ làm việc với chuỗi. Lấy các tài khoản có số dư trên 10 triệu:
▶ Chạy được trong SQL Builder
SELECT account_no, balance, currency
FROM accounts
WHERE balance > 10000000;
Các toán tử so sánh thường dùng: = (bằng), <> hoặc != (khác), > < >= <=. Vì balance là số (NUMERIC) nên không cần dấu nháy.
Ví dụ 5: Lọc trên giá trị phân loại
Lấy tất cả giao dịch ghi nợ:
▶ Chạy được trong SQL Builder
SELECT id, account_id, amount, created_at
FROM transactions
WHERE kind = 'debit';
Cột kind chỉ nhận 'credit' hoặc 'debit', nên điều kiện này tách riêng những lần tiền bị trừ khỏi tài khoản. Đây là kiểu lọc bạn sẽ dùng rất thường xuyên khi phân tích dòng tiền.
Tóm tắt
- RDBMS quản lý dữ liệu dưới dạng các bảng gồm hàng và cột; các bảng liên kết qua giá trị chung.
- Khoá chính định danh duy nhất mỗi hàng; khoá ngoại tham chiếu tới khoá chính của bảng khác, thực thi toàn vẹn tham chiếu.
- Các kiểu dữ liệu phổ biến:
INT,NUMERIC(cho tiền),TEXT/VARCHAR,DATE/TIMESTAMP,BOOLEAN. - NULL nghĩa là "không có giá trị"; kiểm tra bằng
IS NULL/IS NOT NULL, không dùng= NULL. - SQL là ngôn ngữ khai báo: bạn mô tả kết quả mong muốn, RDBMS lo cách thực thi.
- Câu
SELECTcơ bản gồmSELECT <cột> FROM <bảng> WHERE <điều kiện>; xử lý logic theo thứ tựFROM→WHERE→SELECT. - Schema sandbox (
departments,employees,customers,accounts,transactions) sẽ là sân tập xuyên suốt loạt bài.
Tự kiểm tra
- Sự khác nhau giữa khoá chính và khoá ngoại là gì? Cho ví dụ từ schema sandbox.
- Tại sao nên dùng
NUMERICthay vìFLOATđể lưu số dư tài khoản? - Câu lệnh nào sai và vì sao:
WHERE hired_at = NULLhayWHERE hired_at IS NULL? - Viết câu
SELECTlấyfull_namevàcreated_atcủa các khách hàng ở'Đà Nẵng'. - Giải thích vì sao SQL được gọi là ngôn ngữ "khai báo" thay vì "thủ tục".
Đọc tiếp
Bài viết liên quan
Cách index hoạt động (B-Tree), đọc EXPLAIN, seq scan vs index scan và mẫu tối ưu truy vấn.
Khoá, ràng buộc, quan hệ và chuẩn hoá 1NF/2NF/3NF — thiết kế lược đồ đúng từ đầu.
Truy vấn lồng, CTE (WITH), CTE đệ quy và hàm cửa sổ — vũ khí cho phân tích nâng cao.
Kết nhiều bảng đúng cách: các loại JOIN, bẫy thường gặp và phép hợp tập.