
Muốn bắt đầu với SQL, bạn chỉ cần nắm khoảng sáu nhóm câu lệnh: CREATE TABLE để tạo bảng, INSERT để thêm dòng, SELECT để đọc, GROUP BY để tổng hợp, JOIN để ghép bảng, UPDATE và DELETE để sửa, xoá. Bài này đi qua các câu lệnh SQL cơ bản đó trên một bộ dữ liệu cửa hàng nhỏ, viết theo cú pháp PostgreSQL, và chỉ cho bạn cách thử trước khi sửa để không làm hỏng dữ liệu thật.
Chuẩn bị bảng mẫu để thực hành: câu lệnh SQL tạo bảng
Câu lệnh SQL tạo bảng là CREATE TABLE: bạn đặt tên bảng, rồi khai báo từng cột kèm kiểu dữ liệu. Ví dụ giả định của tôi là một cửa hàng phụ kiện điện thoại có ba bảng: sản phẩm, khách hàng và đơn hàng. Bạn có thể chạy thử trên PostgreSQL cài máy hoặc trên Supabase (xem thêm PostgreSQL là gì).
CREATE TABLE san_pham (id bigint generated always as identity primary key, ten text not null, danh_muc text, gia numeric(12,0), ton_kho int default 0);
CREATE TABLE khach_hang (id bigint generated always as identity primary key, ten text not null, so_dien_thoai text);
CREATE TABLE don_hang (id bigint generated always as identity primary key, khach_hang_id bigint references khach_hang(id), san_pham_id bigint references san_pham(id), so_luong int not null, ngay_dat date not null);
Để cho gọn, mỗi đơn hàng ở đây chỉ có một sản phẩm. Hệ thống thật thường tách thêm bảng chi tiết đơn. Dòng references chính là khoá ngoại; nếu chưa rõ khái niệm này, đọc khóa chính khóa ngoại là gì.
Tiếp theo, thêm vài dòng mẫu bằng INSERT:
INSERT INTO san_pham (ten, danh_muc, gia, ton_kho) VALUES ('Ốp lưng', 'Phụ kiện', 80000, 25), ('Cáp sạc', 'Phụ kiện', 60000, 8), ('Tai nghe', 'Âm thanh', 350000, 5), ('Loa mini', 'Âm thanh', 500000, 12);
INSERT INTO khach_hang (ten, so_dien_thoai) VALUES ('Lan', '0900000001'), ('Hùng', NULL), ('Mai', '0900000003');
INSERT INTO don_hang (khach_hang_id, san_pham_id, so_luong, ngay_dat) VALUES (1, 1, 2, '2026-09-03'), (1, 3, 1, '2026-09-10'), (3, 2, 4, '2026-09-15'), (3, 4, 1, '2026-10-02');
Số điện thoại và tên ở trên là dữ liệu bịa để minh hoạ. Lưu ý Hùng chưa có đơn nào, ta sẽ cần chi tiết này ở phần JOIN.
Câu lệnh SQL SELECT: làm sao lấy đúng dữ liệu cần?
Câu lệnh SQL SELECT đọc dữ liệu: bạn nêu cột cần lấy, bảng nguồn và điều kiện lọc. Dạng gọn nhất là SELECT ten, gia FROM san_pham;. Dùng SELECT * được khi khám phá, nhưng khi làm báo cáo nên liệt kê cột để kết quả gọn và ổn định.
Lọc bằng WHERE
WHERE giữ lại những dòng thoả điều kiện. Các toán tử hay dùng:
=,>,<: so sánh thông thường, ví dụWHERE danh_muc = 'Âm thanh'.BETWEEN: khoảng giá trị, gồm cả hai đầu mút.IN: khớp một trong nhiều giá trị, ví dụWHERE danh_muc IN ('Phụ kiện', 'Âm thanh').LIKEvàILIKE: tìm theo mẫu, dấu%thay cho chuỗi bất kỳ.ILIKEkhông phân biệt hoa thường và là của PostgreSQL.IS NULL: tìm ô trống.
Ví dụ 1, sản phẩm tồn kho dưới 10 để nhắc nhập hàng:
SELECT ten, ton_kho FROM san_pham WHERE ton_kho < 10 ORDER BY ton_kho ASC;
Ví dụ 2, đơn trong tháng 9/2026:
SELECT * FROM don_hang WHERE ngay_dat BETWEEN '2026-09-01' AND '2026-09-30';
Ví dụ 3, khách chưa lưu số điện thoại:
SELECT ten FROM khach_hang WHERE so_dien_thoai IS NULL;
Sắp xếp và giới hạn dòng
ORDER BY sắp xếp (ASC tăng dần, DESC giảm dần), còn LIMIT cắt bớt số dòng. Ba sản phẩm giá cao nhất: SELECT ten, gia FROM san_pham ORDER BY gia DESC LIMIT 3;
GROUP BY SQL: tổng hợp như PivotTable
GROUP BY SQL gom các dòng có cùng giá trị ở một cột thành một nhóm, rồi dùng hàm như SUM, COUNT, AVG để tính cho từng nhóm. Nó làm đúng việc của bảng tổng hợp mà bạn quen trong Excel hay pivot table trong Google Sheet, chỉ khác là viết bằng chữ.
SUM(cot): cộng tổng.COUNT(*): đếm số dòng.AVG(cot): trung bình.
Doanh thu theo danh mục (cần ghép với bảng sản phẩm để lấy giá, phần JOIN bên dưới sẽ giải thích):
SELECT sp.danh_muc, SUM(dh.so_luong * sp.gia) AS doanh_thu FROM don_hang dh JOIN san_pham sp ON sp.id = dh.san_pham_id GROUP BY sp.danh_muc;
HAVING: lọc sau khi gom nhóm
HAVING lọc trên kết quả đã tổng hợp, còn WHERE lọc từng dòng trước khi gom. Muốn chỉ giữ danh mục có doanh thu trên 500.000 đồng, thêm vào cuối: HAVING SUM(dh.so_luong * sp.gia) > 500000. Không viết được điều kiện này trong WHERE vì lúc đó tổng chưa được tính.
Doanh thu theo tháng với date_trunc
Hàm date_trunc của PostgreSQL cắt ngày về đầu tháng, giúp gom đơn theo tháng:
SELECT date_trunc('month', dh.ngay_dat) AS thang, SUM(dh.so_luong * sp.gia) AS doanh_thu FROM don_hang dh JOIN san_pham sp ON sp.id = dh.san_pham_id GROUP BY 1 ORDER BY 1;
Số 1 trong GROUP BY và ORDER BY nghĩa là cột đầu tiên trong SELECT. Với MySQL bạn dùng hàm khác, nên hãy tra tài liệu của hệ bạn đang dùng.

JOIN: ghép dữ liệu từ hai bảng thế nào?
JOIN ghép các dòng của hai bảng dựa trên cột chung, thường là khoá ngoại khớp với khoá chính. Có hai kiểu bạn dùng nhiều nhất: INNER JOIN chỉ giữ dòng có cặp ở cả hai bảng, còn LEFT JOIN giữ toàn bộ dòng của bảng bên trái, dòng nào không có cặp thì phần bên phải là NULL.
Đơn hàng kèm tên khách:
SELECT dh.id, kh.ten, dh.ngay_dat FROM don_hang dh INNER JOIN khach_hang kh ON kh.id = dh.khach_hang_id;
Khách chưa từng mua, đây là chỗ LEFT JOIN phát huy:
SELECT kh.ten FROM khach_hang kh LEFT JOIN don_hang dh ON dh.khach_hang_id = kh.id WHERE dh.id IS NULL;
Với dữ liệu mẫu, kết quả là Hùng. Danh sách này rất hữu ích để chạy chương trình chăm sóc lại khách.

Câu lệnh SQL UPDATE, INSERT và DELETE: sửa dữ liệu an toàn
Câu lệnh SQL UPDATE sửa giá trị của các dòng đang có, INSERT thêm dòng mới, DELETE xoá dòng. Hai lệnh sau cùng thay đổi dữ liệu thật, nên hãy tập thói quen thử trước khi chạy.
Ví dụ tăng giá 5% cho nhóm Phụ kiện:
UPDATE san_pham SET gia = gia * 1.05 WHERE danh_muc = 'Phụ kiện';
Quy trình tôi khuyên, theo từng bước:
- Chạy
SELECT * FROM san_pham WHERE danh_muc = 'Phụ kiện';với đúng điều kiện WHERE đó, xem số dòng và nội dung có như bạn nghĩ không. - Mở giao dịch bằng
BEGIN;. - Chạy lệnh UPDATE.
- Chạy lại SELECT để kiểm tra kết quả.
- Hài lòng thì
COMMIT;để lưu. Thấy sai thìROLLBACK;để huỷ mọi thay đổi từ lúc BEGIN.
Xoá cứng hay xoá mềm?
Xoá cứng (DELETE FROM san_pham WHERE id = 4;) làm dòng biến mất hẳn, khó lấy lại. Xoá mềm là thêm cột đánh dấu, ví dụ ALTER TABLE san_pham ADD COLUMN da_xoa boolean default false;, rồi khi "xoá" chỉ chạy UPDATE san_pham SET da_xoa = true WHERE id = 4; và mọi truy vấn thêm WHERE da_xoa = false. Xoá mềm tốn thêm điều kiện lọc nhưng giữ được lịch sử và sửa sai dễ hơn; với đơn hàng, chứng từ thì tôi nghiêng hẳn về xoá mềm.

Bảng tóm tắt các câu lệnh SQL cơ bản
| Lệnh | Dùng để làm gì | Ví dụ một dòng |
|---|---|---|
CREATE TABLE | Tạo bảng mới | CREATE TABLE khach_hang (id bigint primary key, ten text); |
INSERT | Thêm dòng | INSERT INTO khach_hang (ten) VALUES ('Lan'); |
SELECT | Đọc dữ liệu | SELECT ten FROM san_pham WHERE ton_kho < 10; |
GROUP BY | Gom nhóm và tính tổng | SELECT danh_muc, COUNT(*) FROM san_pham GROUP BY danh_muc; |
JOIN | Ghép hai bảng | ... FROM don_hang dh JOIN khach_hang kh ON kh.id = dh.khach_hang_id |
UPDATE | Sửa dòng đã có | UPDATE san_pham SET ton_kho = 30 WHERE id = 1; |
DELETE | Xoá dòng | DELETE FROM don_hang WHERE id = 9; |
Những lỗi người mới hay gặp khi viết SQL
- UPDATE hoặc DELETE thiếu WHERE: lệnh áp dụng cho toàn bộ bảng. Đây là lỗi nguy hiểm nhất, nên luôn SELECT trước và dùng BEGIN.
- Cột trong SELECT không nằm trong GROUP BY: mỗi cột không được tính bằng SUM, COUNT… đều phải nằm trong GROUP BY, nếu không PostgreSQL báo lỗi.
- Nhầm nháy đơn và nháy kép: trong PostgreSQL, nháy đơn dành cho giá trị chữ (
'Phụ kiện'), nháy kép dành cho tên cột hoặc bảng. Dùng nháy kép cho giá trị sẽ bị hiểu là tên cột. - So sánh NULL bằng dấu =:
so_dien_thoai = NULLkhông bao giờ cho kết quả; phải viếtIS NULLhoặcIS NOT NULL. - Khác biệt cú pháp MySQL và PostgreSQL: cột tự tăng của MySQL là
AUTO_INCREMENT, của PostgreSQL làgenerated always as identity. Cách giới hạn và bỏ qua dòng (LIMIT,OFFSET) cũng có chỗ khác nhau, vàILIKEchỉ có ở PostgreSQL. Nếu dùng MySQL, hãy đối chiếu tài liệu chính thức của nó.
Tóm tắt và bước tiếp theo
Tóm lại: tạo bảng bằng CREATE TABLE, đọc bằng SELECT kèm WHERE, tổng hợp bằng GROUP BY, ghép bảng bằng JOIN, và luôn kiểm tra bằng SELECT, BEGIN, ROLLBACK trước khi UPDATE hay DELETE thật. Bước tiếp theo, bạn hãy tự dựng lại ba bảng mẫu ở trên, rồi thử viết thêm các báo cáo của riêng cửa hàng mình, ví dụ top khách mua nhiều nhất hoặc sản phẩm không bán được tháng nào.
Nếu muốn đi theo lộ trình có thứ tự, đọc học SQL cho người mới bắt đầu; còn muốn hiểu bức tranh tổng thể trước, xem SQL là gì.
Câu hỏi thường gặp
›Nên học câu lệnh SQL nào trước?
Hãy bắt đầu với SELECT kèm WHERE và ORDER BY vì đây là nhóm bạn dùng nhiều nhất và không làm thay đổi dữ liệu. Sau đó học GROUP BY để làm báo cáo, rồi JOIN, cuối cùng mới đến UPDATE và DELETE.
›WHERE và HAVING khác nhau thế nào?
WHERE lọc từng dòng trước khi gom nhóm, còn HAVING lọc các nhóm sau khi đã tính SUM, COUNT hay AVG. Ví dụ muốn chỉ lấy đơn tháng 9 thì dùng WHERE, muốn chỉ giữ danh mục có tổng doanh thu trên một mức nào đó thì dùng HAVING.
›Lỡ UPDATE nhầm cả bảng thì làm sao?
Nếu bạn đang trong giao dịch chưa COMMIT, hãy chạy ROLLBACK ngay để huỷ thay đổi. Nếu đã COMMIT, cách còn lại là khôi phục từ bản sao lưu, nên hãy kiểm tra nhà cung cấp cơ sở dữ liệu của bạn có sao lưu hay không và khôi phục được đến thời điểm nào.
›Câu lệnh SQL trên MySQL và PostgreSQL có giống nhau không?
Phần lõi như SELECT, WHERE, GROUP BY, JOIN, UPDATE giống nhau gần hết. Khác biệt nằm ở chi tiết như cột tự tăng (AUTO_INCREMENT so với identity), hàm xử lý ngày tháng và một số toán tử như ILIKE chỉ có ở PostgreSQL.
Tác giả
Chuyên gia chuyển đổi số · Đại diện 5F Edu
Hơn 8 năm triển khai và tư vấn chuyển đổi số cho doanh nghiệp vừa và nhỏ: chuẩn hoá quy trình, xây phần mềm quản lý bằng AppSheet và web app, tự động hoá bằng N8N. Người làm kênh YouTube Nghiện AppSheet.



