Buổi 1: Giới thiệu SQL và cấu trúc câu lệnh SELECT cơ bản
Chào mừng các anh chị đến với buổi đầu tiên của khóa đào tạo SQL. Trong 3 giờ tới chúng ta sẽ đi từ câu hỏi "SQL là gì?" đến việc tự viết được những truy vấn lọc và sắp xếp dữ liệu khách hàng, tài khoản, lệnh giao dịch – đúng loại yêu cầu mà bộ phận IT, phân tích và back-office của một công ty chứng khoán gặp hằng ngày: "cho tôi danh sách tài khoản số dư lớn", "khách hàng nào chưa có email để gửi thông báo?", "lệnh bán nào khớp gần đây?".
Cách học của khóa: học đến đâu làm đến đó. Sau mỗi phần lý thuyết ngắn sẽ có khung Thực hành ngay tại chỗ; các ví dụ SQL trong bài có nút Chạy thử để bạn tự bấm xem kết quả trên bộ dữ liệu mẫu.
Mục tiêu buổi học
Sau buổi này bạn có thể:
- Giải thích SQL là gì, các khái niệm Database, Table, Row, Column và vai trò của SQL trong công ty chứng khoán.
- Đọc hiểu cấu trúc bộ dữ liệu mẫu của khóa học (Customers, Accounts, Securities, MarketPrices, Orders, Trades, Portfolio, CustomerStats, AuditLog) và các quan hệ giữa chúng.
- Viết câu lệnh
SELECT ... FROM ..., đặt bí danh cột và loại trùng bằngDISTINCT. - Lọc dữ liệu bằng
WHEREvới các toán tử so sánh,AND/OR/NOT(đúng độ ưu tiên),LIKE,IN,BETWEEN,IS NULL. - Sắp xếp kết quả với
ORDER BY(ASC/DESC, nhiều cột, thứ tự ưu tiên). - Chạy và nộp bài SQL trên Codelab bằng SQL Server (T-SQL) hoặc MySQL.
Bản đồ buổi học
| Phần | Nội dung | Hình thức | Thời lượng |
|---|---|---|---|
| 1 | SQL là gì, vai trò, các hệ quản trị CSDL | Lý thuyết | 12 phút |
| 2–3 | Bộ dữ liệu mẫu, công cụ, cách nộp bài trên Codelab | Lý thuyết + demo | 13 phút |
| 4 | Cấu trúc SELECT, bí danh, DISTINCT + bài 1.1 |
7 phút lý thuyết + 18 phút thực hành | 25 phút |
| 5.1–5.3 | FROM, WHERE, so sánh, AND/OR/NOT + bài 1.2 |
8 phút lý thuyết + 27 phút thực hành | 35 phút |
| 5.4–5.5 | LIKE, IN, BETWEEN, NULL + bài 1.3 |
8 phút lý thuyết + 34 phút thực hành | 42 phút |
| 6 | ORDER BY + bài 1.4 |
8 phút lý thuyết + 36 phút thực hành | 44 phút |
| 7–9 | Áp dụng nghiệp vụ, sai lầm thường gặp, tóm tắt, chữa bài, Q&A | Thực hành + thảo luận (làm lại bài khó, đổi chéo chữa bài) | 9 phút |
| Tổng | ~56 phút lý thuyết + demo, ~124 phút thực hành (~69%) | 180 phút |
1. SQL là gì? Tại sao cần học SQL?
SQL (Structured Query Language) là ngôn ngữ chuẩn để hỏi và thao tác dữ liệu trong cơ sở dữ liệu quan hệ. Bạn không cần chỉ cho máy "làm thế nào" (duyệt từng dòng, so sánh từng ô); bạn chỉ mô tả "muốn lấy dữ liệu gì", hệ quản trị tự tìm cách nhanh nhất.
Ví dụ nghiệp vụ: cuối ngày, phòng Quản lý rủi ro cần danh sách tài khoản ký quỹ có số dư dưới ngưỡng. Với Excel, bạn phải mở file, lọc, sao chép thủ công mỗi ngày. Với SQL, một câu lệnh 3 dòng chạy trên dữ liệu thật, luôn mới nhất, và tái sử dụng được mỗi ngày.
1.1. Bốn khái niệm nền tảng
| Khái niệm | Ý nghĩa | Ví dụ trong công ty chứng khoán |
|---|---|---|
| Database (cơ sở dữ liệu) | Nơi lưu toàn bộ dữ liệu của một hệ thống | CSDL hnx_securities của khóa học |
| Table (bảng) | Một tập hợp dữ liệu cùng chủ đề, giống một trang tính Excel | Customers (khách hàng), Orders (lệnh) |
| Row (hàng, bản ghi) | Một đối tượng cụ thể trong bảng | Khách hàng "Nguyen Van An" |
| Column (cột, trường) | Một thuộc tính của đối tượng, có kiểu dữ liệu cố định | full_name, city, birth_date |
Hình dung bảng Customers như sau (mỗi dòng ngang là một khách hàng, mỗi cột dọc là một thuộc tính):
| customer_id | full_name | customer_type | city |
|---|---|---|---|
| 1 | Nguyen Van An | INDIVIDUAL | Ha Noi |
| 2 | Tran Thi Binh | INDIVIDUAL | Da Nang |
| 31 | Ngan Hang TMCP Dau Tu Viet | INSTITUTION | TP.HCM |
1.2. Vai trò của SQL trong công ty chứng khoán
- Báo cáo: số lượng tài khoản mở mới trong tháng, tổng giá trị khớp lệnh theo mã, báo cáo gửi Sở giao dịch.
- Phân tích: nhóm khách hàng theo thành phố/độ tuổi, mã cổ phiếu được giao dịch nhiều nhất, hiệu quả từng kênh giới thiệu.
- Kiểm soát rủi ro: tài khoản margin có số dư thấp, lệnh bất thường về khối lượng, khách hàng bị khóa vẫn phát sinh lệnh.
- Vận hành/IT: đối soát dữ liệu giữa các hệ thống, kiểm tra dữ liệu sau khi nâng cấp, sửa dữ liệu lỗi có kiểm soát.
1.3. Các hệ quản trị CSDL phổ biến
| Hệ quản trị | Nhà cung cấp | Ghi chú |
|---|---|---|
| SQL Server | Microsoft | Công cụ chính của khóa học (ngôn ngữ T-SQL, công cụ SSMS) |
| MySQL | Oracle (mã nguồn mở) | Phương án thứ hai của khóa học |
| PostgreSQL | Cộng đồng | Mã nguồn mở, mạnh về chuẩn SQL |
| Oracle Database | Oracle | Phổ biến ở hệ thống lõi của ngân hàng/chứng khoán lớn (ngôn ngữ PL/SQL) |
Tất cả đều nói "SQL" và khoảng 80% cú pháp giống nhau. Phần khác biệt (thường là hàm và cách giới hạn số dòng) sẽ được đánh dấu SQL Server / MySQL trong bài giảng.
SQL Server vs MySQL: mọi bài tập của khóa học đều phải chạy đúng trên cả hai hệ, nên khi học hãy ưu tiên cú pháp chung. Bạn chỉ cần chọn một hệ để làm bài (khuyến nghị SQL Server vì đó là công cụ chính); khi gặp cú pháp riêng, bài giảng luôn cho cả hai phiên bản.
1.4. SQL gồm những nhóm lệnh nào?
Câu lệnh SQL chia thành các nhóm theo mục đích. Khóa học đi từ "đọc" đến "ghi":
| Nhóm | Viết tắt | Lệnh tiêu biểu | Học ở buổi |
|---|---|---|---|
| Truy vấn dữ liệu | DQL | SELECT |
1–5, 8 |
| Thao tác dữ liệu | DML | INSERT, UPDATE, DELETE |
6 |
| Định nghĩa cấu trúc | DDL | CREATE, ALTER, DROP |
7 |
| Điều khiển giao dịch | TCL | COMMIT, ROLLBACK |
6, 10 |
Buổi hôm nay chỉ dùng SELECT – lệnh chỉ đọc, không làm thay đổi dữ liệu, nên bạn có thể thử thoải mái mà không sợ hỏng gì.
2. Bộ dữ liệu mẫu của khóa học
Toàn bộ khóa dùng chung một CSDL chứng khoán nhỏ tên hnx_securities. Trên Codelab, mỗi lần chạy bài, hệ thống nạp lại đúng bộ dữ liệu này (bản sao riêng của bạn, thoải mái UPDATE/DELETE ở các buổi sau mà không ảnh hưởng ai). Dữ liệu không dấu (Ha Noi, Nguyen Van An...) để tránh lỗi mã hóa giữa hai hệ. "Hôm nay" của bộ dữ liệu cố định là 2026-09-25 – các bài chấm không dùng GETDATE()/NOW().
Lưu ý: MySQL trên Linux phân biệt chữ hoa/thường ở tên bảng: luôn viết đúng
Customers,Accounts,MarketPrices... như bên dưới.
2.1. Bức tranh tổng thể
Trước khi đi vào từng cột, hãy nhìn toàn cảnh: 9 bảng nối với nhau như thế nào. Hình dưới là sơ đồ quan hệ (ER) của bộ dữ liệu.
PK = khóa chính, FK = khóa ngoài. Đường nối: 1 ở phía bảng 'một', n ở phía bảng 'nhiều'. Đường nét đứt: liên hệ logic (không khai báo FK). Quan hệ n–n giữa Accounts và Securities đi qua bảng Portfolio.
Cách đọc hình: mỗi khung là một bảng; các đường nối là khóa ngoại – cột của bảng này trỏ về khóa chính của bảng kia. Đi theo đường nối từ trái sang phải là đi theo dòng nghiệp vụ: một khách hàng mở nhiều tài khoản, mỗi tài khoản đặt nhiều lệnh, mỗi lệnh khớp thành một hay nhiều giao dịch (Trades). Securities là danh mục mã, được Orders, Trades, Portfolio, MarketPrices tham chiếu qua cột symbol.
2.2. Chi tiết từng bảng
Customers – khách hàng (40 dòng: 30 cá nhân INDIVIDUAL, 10 tổ chức INSTITUTION)
| Cột | Kiểu | Ghi chú |
|---|---|---|
customer_id |
INT | Khóa chính (PK) |
customer_code |
NVARCHAR(10) | Mã khách hàng, duy nhất, dạng KH100007 |
full_name |
NVARCHAR(120) | Họ tên (cá nhân) hoặc tên tổ chức |
customer_type |
NVARCHAR(12) | INDIVIDUAL (cá nhân) / INSTITUTION (tổ chức) |
birth_date |
DATE, cho phép NULL | Ngày sinh; NULL với tổ chức |
city |
NVARCHAR(30) | Ha Noi, TP.HCM, Da Nang, Hai Phong, Can Tho |
phone |
NVARCHAR(20) | Số điện thoại |
email |
NVARCHAR(100), cho phép NULL | Một số khách hàng chưa có email (NULL) |
status |
NVARCHAR(10) | ACTIVE / INACTIVE / NEW |
created_date |
DATE | Ngày mở hồ sơ |
referrer_id |
INT, cho phép NULL | Khóa ngoại (FK) tới Customers.customer_id – người giới thiệu |
Accounts – tài khoản giao dịch (93 dòng; một khách hàng có 1–4 tài khoản)
| Cột | Kiểu | Ghi chú |
|---|---|---|
account_id |
INT | PK |
customer_id |
INT | FK → Customers.customer_id |
account_no |
NVARCHAR(10) | Số tài khoản, duy nhất, dạng ACC00001 |
account_type |
NVARCHAR(10) | CASH (tiền mặt) / MARGIN (ký quỹ) |
balance |
DECIMAL(18,2) | Số dư tiền (đồng) |
opened_date |
DATE | Ngày mở tài khoản |
status |
NVARCHAR(10) | ACTIVE / CLOSED |
Securities – danh mục cổ phiếu (12 mã)
| Cột | Kiểu | Ghi chú |
|---|---|---|
symbol |
NVARCHAR(10) | PK – mã chứng khoán (FPT, VCB, HPG...) |
company_name |
NVARCHAR(150) | Tên công ty |
exchange |
NVARCHAR(10) | Sàn: HOSE / HNX |
sector |
NVARCHAR(60) | Ngành |
listed_date |
DATE | Ngày niêm yết |
par_value |
INT | Mệnh giá (10.000 đ) |
MarketPrices – giá thị trường theo phiên (120 dòng = 12 mã × 10 phiên, 14/09–25/09/2026)
| Cột | Kiểu | Ghi chú |
|---|---|---|
symbol, trade_date |
NVARCHAR(10), DATE | PK gồm hai cột; symbol là FK → Securities |
open_price, high_price, low_price, close_price |
DECIMAL(12,2) | Giá mở cửa, cao nhất, thấp nhất, đóng cửa |
volume |
BIGINT | Khối lượng khớp trong phiên |
Orders – lệnh đặt (120 dòng)
| Cột | Kiểu | Ghi chú |
|---|---|---|
order_id |
INT | PK |
account_id |
INT | FK → Accounts.account_id |
symbol |
NVARCHAR(10) | FK → Securities.symbol |
side |
NVARCHAR(4) | BUY (mua) / SELL (bán) |
order_type |
NVARCHAR(2) | LO (lệnh giới hạn) / MP (lệnh thị trường) |
quantity, price |
INT, DECIMAL(12,2) | Khối lượng, giá đặt |
order_date |
DATE | Ngày đặt |
status |
NVARCHAR(10) | MATCHED (khớp hết), PARTIAL (khớp một phần), PENDING (chờ khớp), CANCELLED (đã hủy) |
Các bảng còn lại
| Bảng | Nội dung | Cột chính |
|---|---|---|
| Trades (86 dòng) | Các lần khớp lệnh | trade_id (PK), order_id (FK → Orders), symbol (FK → Securities), quantity, price, trade_date |
| Portfolio (209 dòng) | Danh mục nắm giữ | PK gồm account_id + symbol (đều là FK), quantity (số cổ phiếu đang giữ), avg_cost (giá vốn bình quân) |
| CustomerStats (40 dòng) | Thống kê giao dịch theo khách hàng | customer_id (PK, FK → Customers), total_trade_value, trade_count |
| AuditLog | Nhật ký đổi trạng thái khách hàng | log_id (PK, tự tăng), customer_id, old_status, new_status, changed_at. Trống ban đầu, sẽ được trigger ghi vào ở Buổi 9 |
2.3. Các quan hệ giữa bảng
- Một khách hàng có nhiều tài khoản (
Accounts.customer_id). - Một tài khoản có nhiều lệnh (
Orders.account_id); một lệnh có thể sinh ra nhiều lần khớp (Trades.order_id). - Portfolio cho biết tài khoản nào đang giữ bao nhiêu cổ phiếu mã nào.
Customers.referrer_idtrỏ ngược lại chính bảngCustomers(khách hàng giới thiệu khách hàng).CustomerStatslà bảng tổng hợp 1–1 vớiCustomers.
Lưu ý: việc ghép nhiều bảng (
JOIN) được học ở Buổi 8. Các buổi đầu chúng ta làm việc trên một bảng mỗi truy vấn. Do đó câu hỏi kiểu "khách hàng Hà Nội có số dư > 50 triệu" (cần cảCustomers.citylẫnAccounts.balance) tạm thời được chia thành các câu hỏi trên từng bảng; ở cuối bài có một gợi ý mở rộng.
Hãy xem thử nội dung một bảng nhỏ nhất – danh mục 12 mã chứng khoán:
SELECT * FROM Securities ORDER BY symbol;CEO Tap doan CEO HNX Bat dong san 2016-10-19 10000
FPT Tap doan FPT HOSE Cong nghe 2006-12-13 10000
HPG Tap doan Hoa Phat HOSE Thep 2007-11-15 10000
IDC Tong Cong ty IDICO HNX Ha tang khu cong nghiep 2016-12-16 10000
MBS Chung khoan MB HNX Chung khoan 2013-07-08 10000
... (12 dòng)3. Công cụ và cách chạy bài trên Codelab
3.1. Công cụ làm việc
- SQL Server Management Studio (SSMS) – công cụ chính. Kết nối tới server, chọn database
hnx_securitiestrong hộp thả xuống ở thanh công cụ (hoặc gõUSE hnx_securities;), bấm New Query, gõ câu lệnh, bôi đen rồi nhấn F5 (hoặc nút Execute). Kết quả hiện ở lưới Results phía dưới. - MySQL Workbench – công cụ tương đương cho MySQL: mở tab SQL, nhấn Ctrl+Enter để chạy câu lệnh tại vị trí con trỏ.
- DBeaver – công cụ miễn phí kết nối được cả SQL Server, MySQL, PostgreSQL, Oracle; cũng chạy bằng Ctrl+Enter.
Mẹo: trong lớp, mỗi người tự chọn một công cụ để khám phá dữ liệu (chuột phải bảng → xem 100 dòng đầu; mở nhánh Columns để xem kiểu dữ liệu), còn bài tập được nộp trên Codelab.
3.2. Cách chạy và nộp bài trên Codelab
- Mở bài, đọc đề, xem bảng dữ liệu liên quan và ví dụ mẫu (dữ liệu gốc cùng kết quả mong đợi).
- Chọn ngôn ngữ SQL Server (T-SQL) hoặc MySQL ở ô chọn ngôn ngữ. Bài nào cũng có lời giải cho cả hai; chọn hệ bạn đang học.
- Viết đúng một câu lệnh
SELECTtrả về kết quả cần tìm (các buổi sau có bài chỉ cầnINSERT/UPDATE/..., đề sẽ nói rõ). - Bấm Nộp bài. Codelab chạy câu lệnh của bạn trên nhiều bộ dữ liệu khác nhau (dữ liệu gốc và các biến thể có thêm/sửa dòng, có NULL, có giá trị đúng biên...). Kết quả được so khớp với đáp án theo từng dòng.
Vì so khớp theo dòng nên hãy chú ý:
- Thứ tự cột đúng như đề, thứ tự dòng đúng như yêu cầu
ORDER BY. - Kết quả không in tên cột, giá trị phân tách bằng ký tự TAB,
NULLin làNULL, số tiềnDECIMALin đủ 2 chữ số thập phân (150000000.00), ngày inyyyy-mm-dd.
Cảnh báo: đừng viết cứng kết quả theo dữ liệu gốc (ví dụ
WHERE customer_id IN (2, 4, 5)khi đề hỏi "số dư > 100 triệu"): các bộ dữ liệu ẩn sẽ làm lời giải như vậy sai.
4. Cấu trúc cơ bản của câu lệnh SELECT
4.1. Cú pháp tối thiểu
SELECT cột_1, cột_2, ...
FROM tên_bảng;SELECTchọn cột nào được hiển thị (phép chiếu).FROMchọn bảng lấy dữ liệu.- Dấu
;kết thúc câu lệnh (T-SQL không bắt buộc nhưng nên viết; MySQL cần khi có nhiều câu lệnh). - Từ khóa SQL không phân biệt hoa/thường; quy ước của khóa: từ khóa VIẾT HOA, tên bảng/cột giữ đúng như trong sơ đồ.
- Chú thích:
-- ghi chú một dònghoặc/* ghi chú nhiều dòng */(cả hai hệ đều hiểu). Hãy ghi chú ý định cho câu lệnh phức tạp.
Ví dụ – tên và thành phố của khách hàng:
SELECT customer_id, full_name, city
FROM Customers
ORDER BY customer_id;1 Nguyen Van An Ha Noi
2 Tran Thi Binh Da Nang
3 Le Van Cuong Ha Noi
4 Pham Thi Dung Ha Noi
5 Hoang Van Em Ha Noi
... (40 dòng)Dấu * lấy tất cả cột (như ví dụ SELECT * FROM Securities ở mục 2) – tiện để khám phá dữ liệu, nhưng không nên dùng trong báo cáo/ứng dụng thật: thứ tự cột và số cột có thể đổi khi bảng thay đổi, và lấy thừa dữ liệu.
4.2. Bí danh cột và loại trùng
Bí danh cột với AS đổi tên cột hiển thị (rất hữu ích khi làm báo cáo):
SELECT full_name AS ten_khach_hang, city AS thanh_pho
FROM Customers
WHERE customer_id <= 3
ORDER BY customer_id;Nguyen Van An Ha Noi
Tran Thi Binh Da Nang
Le Van Cuong Ha NoiLoại trùng với DISTINCT: có bao nhiêu khách hàng khác nhau sở hữu tài khoản trên 100 triệu, mỗi khách hàng chỉ kể một lần:
SELECT DISTINCT customer_id
FROM Accounts
WHERE balance > 100000000
ORDER BY customer_id;2
4
5
6
7
... (27 dòng)Không có DISTINCT, khách hàng có 3 tài khoản lớn sẽ xuất hiện 3 lần. DISTINCT áp dụng cho cả bộ cột liệt kê sau nó: SELECT DISTINCT city, customer_type ... loại trùng theo cặp giá trị.
Mẹo: cột bí danh có khoảng trắng phải đặt trong ngoặc –
[Ten cot](SQL Server) hoặc`Ten cot`(MySQL). Khóa học dùng bí danh không dấu, có gạch dưới (ten_khach_hang) để chạy được ở cả hai hệ.
4.3. Thứ tự xử lý logic của một câu SELECT
Câu lệnh được viết theo thứ tự SELECT → FROM → WHERE → ORDER BY, nhưng hệ quản trị hiểu và thực hiện theo một thứ tự khác. Hình dưới là "bản đồ" thứ tự xử lý logic – bạn sẽ quay lại nó ở mọi buổi sau này.
- 1FROMChọn bảng / JOIN
- 2WHERELọc từng dòng
- 3GROUP BYGom nhóm
- 4HAVINGLọc nhóm
- 5SELECTChọn cột, tính biểu thức
- 6ORDER BYSắp xếp
- 7LIMIT / TOPLấy N dòng
Bạn viết SELECT trước nhưng SQL xử lý FROM trước. Vì vậy WHERE không dùng được bí danh (alias) vừa đặt ở SELECT, còn ORDER BY thì dùng được.
Cách đọc hình: đi từ trên xuống dưới. FROM chọn bảng nguồn trước tiên; WHERE giữ lại những dòng thỏa điều kiện; SELECT chọn cột (và đặt bí danh); ORDER BY sắp xếp kết quả cuối cùng. Các bước GROUP BY, HAVING, TOP/LIMIT trong hình sẽ được học ở Buổi 2 – hiện tại bạn chỉ cần nhớ chúng nằm ở đâu trong dòng chảy.
Hệ quả thực tế của thứ tự này:
- Bí danh đặt ở
SELECTkhông dùng được trongWHEREvìWHEREchạy trướcSELECT(lúc đó bí danh chưa tồn tại). Ngược lạiORDER BYchạy sau nên dùng được bí danh. WHEREloại dòng trước khi chọn cột, nên lọc sớm giúp hệ quản trị làm ít việc hơn.
4.4. Lọc dòng đầu tiên với WHERE
Chưa cần học hết WHERE, bạn đã có thể lọc theo một giá trị: thêm WHERE cột = 'giá trị' giữa FROM và ORDER BY. Ví dụ – chỉ lấy khách hàng tổ chức:
SELECT customer_id, customer_code, full_name, city
FROM Customers
WHERE customer_type = 'INSTITUTION'
ORDER BY customer_id;31 KH100217 Ngan Hang TMCP Dau Tu Viet TP.HCM
32 KH100224 Ngan Hang Nong Nghiep Phat Trien Ha Noi
33 KH100231 Cong ty TNHH Dau tu Minh Phat Ha Noi
34 KH100238 Quy Dau tu Bao Viet TP.HCM
35 KH100245 Cong ty CP Chung khoan An Binh Ha Noi
... (10 dòng)Chuỗi đặt trong nháy đơn, so sánh chuỗi mặc định không phân biệt hoa/thường trên cả hai hệ. Mục 5 sẽ đi sâu vào WHERE.
Thực hành: vừa học xong SELECT, FROM, bí danh và một điều kiện WHERE đơn giản, hãy làm bài 1.1 để lấy danh sách toàn bộ khách hàng rồi lọc riêng nhóm khách hàng tổ chức.
5. Mệnh đề FROM và WHERE
5.1. Chọn bảng dữ liệu
Việc đầu tiên khi gặp một câu hỏi nghiệp vụ là chọn đúng bảng:
| Câu hỏi | Bảng |
|---|---|
| Ai là khách hàng, ở đâu, loại gì? | Customers |
| Tài khoản nào, số dư bao nhiêu? | Accounts |
| Mã nào niêm yết ở sàn nào? | Securities |
| Giá đóng cửa từng phiên? | MarketPrices |
| Lệnh mua/bán nào đã đặt, đã khớp? | Orders, Trades |
5.2. Lọc dòng với WHERE và các toán tử so sánh
WHERE chỉ giữ lại những dòng thỏa điều kiện.
| Toán tử | Ý nghĩa | Ví dụ |
|---|---|---|
= |
bằng | city = 'Ha Noi' |
<> hoặc != |
khác | status <> 'ACTIVE' |
<, > |
nhỏ hơn, lớn hơn | balance > 100000000 |
<=, >= |
nhỏ hơn/bằng, lớn hơn/bằng | quantity >= 1000 |
Lưu ý: trong chuẩn SQL, "khác" viết là
<>;!=cũng chạy được trên cả SQL Server và MySQL. Khóa học dùng<>cho gọn nhất quán.
Quy tắc viết giá trị:
- Chuỗi và ngày đặt trong dấu nháy đơn:
'Ha Noi','2026-09-25'(ngày dạngyyyy-mm-ddchạy đúng trên cả hai hệ). - Số không có nháy, không có dấu phân cách hàng nghìn:
100000000, không phải100.000.000hay100,000,000. Để dễ đọc, nhớ 100 triệu = 1 và 8 số 0.
Ví dụ – tài khoản có số dư lớn hơn 100 triệu:
SELECT account_id, customer_id, balance
FROM Accounts
WHERE balance > 100000000
ORDER BY account_id;4 2 8600000000.00
8 4 230000000.00
9 4 1250000000.00
11 5 3400000000.00
12 6 780000000.00
... (46 dòng)Ví dụ – khách hàng ở Hà Nội (dữ liệu không dấu nên viết 'Ha Noi'), và khách hàng mở hồ sơ từ đầu tháng 9/2026:
SELECT customer_id, full_name, created_date
FROM Customers
WHERE created_date >= '2026-09-01'
ORDER BY created_date, customer_id;19 Cao Van Yen 2026-09-14
12 Ly Thi Quynh 2026-09-19
5 Hoang Van Em 2026-09-23
26 Hoang Thi Huong 2026-09-23SQL Server vs MySQL: so sánh chuỗi mặc định không phân biệt hoa/thường trên cả hai hệ (collation
CI), nên'ha noi'cũng khớp'Ha Noi'. Tuy vậy hãy viết đúng hoa/thường như dữ liệu. Chuỗi Unicode có dấu trong SQL Server nên viếtN'Hà Nội'(tiền tốN); dữ liệu khóa học không dấu nên không cần.
Sai lầm thường gặp
| Sai lầm | Hậu quả | Cách đúng |
|---|---|---|
city = Ha Noi (quên nháy) |
Lỗi cú pháp | city = 'Ha Noi' |
city = "Ha Noi" (nháy kép) |
MySQL chấp nhận, SQL Server hiểu là tên cột → lỗi | Luôn dùng nháy đơn |
city = 'Hà Nội' (có dấu) |
Không ra dòng nào vì dữ liệu không dấu | city = 'Ha Noi' |
balance > 100.000.000 |
Lỗi cú pháp/sai giá trị | balance > 100000000 |
5.3. Kết hợp nhiều điều kiện: AND, OR, NOT
| Toán tử | Dòng được giữ khi... |
|---|---|
A AND B |
cả hai điều kiện đúng |
A OR B |
ít nhất một điều kiện đúng |
NOT A |
điều kiện A sai |
Ví dụ – khách hàng cá nhân, đang hoạt động, ở Hà Nội:
SELECT customer_id, full_name, city
FROM Customers
WHERE customer_type = 'INDIVIDUAL' AND status = 'ACTIVE' AND city = 'Ha Noi'
ORDER BY customer_id;1 Nguyen Van An Ha Noi
3 Le Van Cuong Ha Noi
7 Dang Van Khoa Ha Noi
8 Bui Thi Lan Ha Noi
10 Ngo Thi Nga Ha Noi
... (12 dòng)Độ ưu tiên: NOT → AND → OR. AND "dính" chặt hơn OR, giống phép nhân so với phép cộng. Khi trộn hai loại, luôn dùng ngoặc để thể hiện ý định. So sánh hai câu sau, cùng nhằm tìm tài khoản đang ACTIVE thuộc loại MARGIN hoặc có số dư trên 1 tỷ:
-- SAI (thiếu ngoặc): hiểu là (ACTIVE và MARGIN) HOẶC (số dư > 1 tỷ)
SELECT COUNT(*) AS so_tai_khoan FROM Accounts
WHERE status = 'ACTIVE' AND account_type = 'MARGIN' OR balance > 1000000000;45-- ĐÚNG: ACTIVE và (MARGIN hoặc số dư > 1 tỷ)
SELECT COUNT(*) AS so_tai_khoan FROM Accounts
WHERE status = 'ACTIVE' AND (account_type = 'MARGIN' OR balance > 1000000000);44Chênh lệch 1 dòng chính là tài khoản ACC00009 – loại MARGIN, số dư 1,25 tỷ nhưng đã CLOSED – lọt vào kết quả của câu sai.
Cảnh báo: trong báo cáo rủi ro, một tài khoản đã đóng vẫn lọt vào danh sách theo dõi là loại sai sót khó phát hiện vì câu lệnh vẫn chạy "bình thường". Thiếu ngoặc không báo lỗi – nó chỉ trả sai kết quả.
NOT đảo điều kiện: WHERE NOT city = 'Ha Noi' tương đương WHERE city <> 'Ha Noi'. NOT hay dùng nhất ở dạng NOT IN, NOT LIKE, NOT BETWEEN, IS NOT NULL.
Thực hành: vừa học xong WHERE, các toán tử so sánh và AND/OR/NOT, hãy làm bài 1.2 để tìm tài khoản số dư lớn, khách hàng theo thành phố và lập danh sách tài khoản cần theo dõi (chú ý ngoặc!).
5.4. LIKE, IN, BETWEEN
LIKE – so khớp mẫu chuỗi
| Ký tự đại diện | Ý nghĩa |
|---|---|
% |
chuỗi bất kỳ (kể cả rỗng) |
_ |
đúng một ký tự |
| Mẫu | Khớp với | Không khớp |
|---|---|---|
'Nguyen%' |
Nguyen Van An, Nguyen Thi Chi |
Tran Thi Binh |
'%Ngan Hang%' |
Ngan Hang TMCP Dau Tu Viet |
Quy Dau tu Bao Viet |
'KH10002_' |
KH100021, KH100028 |
KH100014, KH100035 |
-- Họ Nguyen
SELECT customer_id, full_name FROM Customers WHERE full_name LIKE 'Nguyen%' ORDER BY customer_id;1 Nguyen Van An
22 Nguyen Thi Chi-- Tên khách hàng có chứa "Ngan Hang"
SELECT customer_id, full_name
FROM Customers
WHERE full_name LIKE '%Ngan Hang%'
ORDER BY customer_id;31 Ngan Hang TMCP Dau Tu Viet
32 Ngan Hang Nong Nghiep Phat Trien
37 Ngan Hang Quan Doi Mien Bac
40 Ngan Hang Ban Le Sai GonLưu ý: mẫu bắt đầu bằng
%('%Ngan Hang%') buộc hệ quản trị đọc toàn bảng, không tận dụng được chỉ mục (Buổi 10). Với bảng vài triệu dòng hãy cân nhắc. Muốn tìm đúng ký tự%hay_thì phải "thoát" (escape) – tình huống hiếm, sẽ không gặp trong khóa học.
SQL Server vs MySQL: SQL Server còn hỗ trợ lớp ký tự
[...]trongLIKE(LIKE '[NT]%') mà MySQL không có – vì vậy bài của khóa dùngORthay vì lớp ký tự.
IN – thuộc một danh sách
SELECT customer_id, customer_code, full_name
FROM Customers
WHERE customer_code IN ('KH100007', 'KH100056', 'KH100224')
ORDER BY customer_code;1 KH100007 Nguyen Van An
8 KH100056 Bui Thi Lan
32 KH100224 Ngan Hang Nong Nghiep Phat TrienIN (a, b, c) tương đương = a OR = b OR = c nhưng gọn hơn nhiều. Dạng phủ định NOT IN dùng để loại trừ – ví dụ đếm khách hàng ở tỉnh khác Hà Nội và TP.HCM:
SELECT COUNT(*) AS so_khach_hang_tinh_khac
FROM Customers
WHERE city NOT IN ('Ha Noi', 'TP.HCM');11BETWEEN – trong một khoảng, bao gồm cả hai đầu mút
SELECT customer_id, full_name, created_date
FROM Customers
WHERE created_date BETWEEN '2021-01-01' AND '2021-12-31'
ORDER BY created_date;14 Phan Thi Thao 2021-02-21
32 Ngan Hang Nong Nghiep Phat Trien 2021-04-17
22 Nguyen Thi Chi 2021-05-30
1 Nguyen Van An 2021-06-29
... (9 dòng)x BETWEEN a AND b = x >= a AND x <= b. Với ngày, a phải nhỏ hơn hoặc bằng b (đảo ngược sẽ không cho dòng nào). Với NOT BETWEEN ta lấy phần ngoài khoảng.
Cảnh báo:
BETWEENgồm cả hai đầu – số dư đúng bằng 100 triệu có thuộcBETWEEN 100000000 AND 500000000, còn> 100000000thì không. Khi đề nói "từ … đến …" (bao gồm) dùngBETWEEN; khi đề nói "lớn hơn" (hẳn) dùng>.
5.5. NULL – giá trị "không biết"
NULL không phải số 0 hay chuỗi rỗng; nó nghĩa là "chưa có / không xác định". Ở bộ dữ liệu: email của 5 khách hàng là NULL, birth_date của tổ chức là NULL, referrer_id của phần lớn khách hàng là NULL.
Mọi so sánh với NULL bằng =, <>, >... đều không đúng cũng không sai (kết quả "không biết"), và dòng bị loại. Cần dùng IS NULL / IS NOT NULL:
SELECT customer_id, full_name, email
FROM Customers
WHERE email IS NULL
ORDER BY customer_id;6 Vu Thi Hoa NULL
12 Ly Thi Quynh NULL
18 Ha Thi Xuan NULL
24 Le Thi Giang NULL
30 Do Thi Nhung NULLBẫy kinh điển: so sánh = NULL không bao giờ cho dòng nào, và <> cũng loại luôn các dòng NULL:
SELECT COUNT(*) AS so_dong FROM Customers WHERE email = NULL;0-- referrer_id <> 1: KHÔNG bao gồm khách chưa có người giới thiệu (NULL)
SELECT COUNT(*) AS so_dong FROM Customers WHERE referrer_id <> 1;6-- Muốn "khác 1 hoặc chưa có người giới thiệu" phải nói rõ
SELECT COUNT(*) AS so_dong FROM Customers WHERE referrer_id <> 1 OR referrer_id IS NULL;38Ví dụ nghiệp vụ: phòng Vận hành chuẩn bị gửi thông báo qua email cho toàn bộ khách hàng. Viết
WHERE email <> ''sẽ bỏ sót nhầm khách chưa có email hoặc cho kết quả khó đoán; cách đúng làWHERE email IS NOT NULL(và cân nhắc thêm chuỗi rỗng khi dữ liệu nhập tay).
Thực hành: vừa học xong LIKE, IN, BETWEEN và NULL, hãy làm bài 1.3 – tìm tên chứa "Ngan Hang", tra cứu theo danh sách mã khách hàng, tài khoản trong khoảng số dư và khách hàng chưa có email.
6. Mệnh đề ORDER BY
Nếu không có ORDER BY, thứ tự dòng trả về không được đảm bảo (có thể đúng theo thứ tự nhập nhưng không có gì cam kết). Muốn kết quả ổn định – đặc biệt khi chấm bài tự động – phải có ORDER BY, và nó luôn nằm cuối câu lệnh.
6.1. ASC và DESC
ASC(mặc định): tăng dần – số nhỏ → lớn, chữ A → Z, ngày cũ → mới.DESC: giảm dần – số lớn → nhỏ, ngày mới → cũ.
SELECT account_id, balance
FROM Accounts
WHERE account_id IN (1, 2, 3, 4)
ORDER BY balance DESC;4 8600000000.00
1 67000000.00
3 32000000.00
2 8000000.00NULL khi sắp xếp: cả SQL Server và MySQL coi NULL là nhỏ nhất – nằm đầu khi ASC, cuối khi DESC. Ví dụ ORDER BY birth_date sẽ đặt các tổ chức (ngày sinh NULL) lên trước:
SELECT customer_id, full_name, birth_date
FROM Customers
ORDER BY birth_date, customer_id;31 Ngan Hang TMCP Dau Tu Viet NULL
32 Ngan Hang Nong Nghiep Phat Trien NULL
33 Cong ty TNHH Dau tu Minh Phat NULL
34 Quy Dau tu Bao Viet NULL
... (40 dòng)6.2. Sắp xếp theo nhiều cột và thứ tự ưu tiên
ORDER BY cột_1 [ASC|DESC], cột_2 [ASC|DESC], ...Cột đứng trước có ưu tiên cao hơn. Hệ quản trị sắp theo cột_1; chỉ khi hai dòng bằng nhau ở cột_1 mới xét tới cột_2, và cứ thế. Mỗi cột có chiều ASC/DESC riêng.
SELECT customer_id, full_name, city, created_date
FROM Customers
WHERE city IN ('Ha Noi', 'TP.HCM') AND customer_type = 'INDIVIDUAL'
ORDER BY city, created_date DESC, customer_id;5 Hoang Van Em Ha Noi 2026-09-23
26 Hoang Thi Huong Ha Noi 2026-09-23
19 Cao Van Yen Ha Noi 2026-09-14
25 Pham Van Hai Ha Noi 2025-07-05
28 Dang Thi Linh Ha Noi 2025-01-02
... (19 dòng)Đọc kết quả: các dòng gom theo thành phố (A → Z); trong cùng thành phố, khách mở hồ sơ mới nhất lên đầu; hai khách cùng ngày (5 và 26 cùng 2026-09-23) thì xếp theo customer_id.
Mẹo: luôn thêm khóa duy nhất (như
customer_id) làm cột sắp xếp cuối cùng để kết quả tất định – hai lần chạy cho cùng một thứ tự – điều bắt buộc khi kết quả được so khớp tự động.
Đổi thứ tự cột trong ORDER BY là đổi ưu tiên: ORDER BY created_date DESC, city sẽ cho một danh sách hoàn toàn khác.
SQL Server vs MySQL: cú pháp
ORDER BYgiống nhau. Cả hai cho phépORDER BY 2 DESC(theo số thứ tự cột trongSELECT), nhưng cách này dễ sai khi sửa danh sách cột; ưu tiên dùng tên cột. Riêng việc giới hạn số dòng (lấy 10 dòng đầu) khác nhau giữa hai hệ – sẽ học ở Buổi 2.
Thực hành: vừa học xong ORDER BY với nhiều cột và thứ tự ưu tiên, hãy làm bài 1.4 – từ sắp xếp lệnh bán, xếp hạng tài khoản đến bài tổng hợp lập danh sách khách hàng chăm sóc.
7. Áp dụng: từ yêu cầu nghiệp vụ đến câu lệnh
Yêu cầu: "Lấy các lệnh mua đã khớp hết, khối lượng từ 1.000 trở lên, đặt trong tháng 9/2026; mới nhất lên trước; cùng ngày thì khối lượng lớn xếp trước, cùng khối lượng thì theo mã lệnh."
Bóc tách: bảng Orders; điều kiện side = 'BUY' AND status = 'MATCHED' AND quantity >= 1000 AND ngày trong khoảng; sắp order_date DESC, quantity DESC, order_id.
SELECT order_id, account_id, symbol, quantity, price, order_date
FROM Orders
WHERE side = 'BUY' AND status = 'MATCHED' AND quantity >= 1000
AND order_date BETWEEN '2026-09-01' AND '2026-09-30'
ORDER BY order_date DESC, quantity DESC, order_id;72 53 NVB 1000 9000.00 2026-09-25
2 80 MBS 2000 25000.00 2026-09-24
38 30 VCS 1000 61000.00 2026-09-24
53 2 SHS 1000 15500.00 2026-09-22
39 53 VCB 1000 92500.00 2026-09-21
... (10 dòng)Quy trình 4 bước bạn nên áp dụng cho mọi bài: (1) xác định bảng → (2) liệt kê từng điều kiện, nối bằng AND/OR, thêm ngoặc → (3) chọn cột theo đúng thứ tự đề yêu cầu → (4) viết ORDER BY có khóa phụ. Sau cùng hãy tự hỏi: "nếu có dòng NULL, dòng đúng biên, dòng đồng hạng thì sao?"
Sai lầm thường gặp
| Sai lầm | Hậu quả | Cách đúng |
|---|---|---|
| Quên nháy đơn quanh chuỗi/ngày, hoặc dùng nháy kép | Lỗi cú pháp hoặc SQL Server hiểu là tên cột | Luôn dùng nháy đơn: 'Ha Noi', '2026-09-25' |
Trộn AND/OR không có ngoặc |
Không báo lỗi nhưng sai kết quả (lọt dòng không mong muốn) | Luôn thêm ngoặc: A AND (B OR C) |
So sánh với NULL bằng = / <> |
Kết quả rỗng hoặc thiếu dòng | Dùng IS NULL / IS NOT NULL |
Quên BETWEEN gồm cả hai đầu mút |
Lệch một số dòng đúng biên | Kiểm tra lại đề "từ … đến …" hay "lớn hơn" |
Dùng bí danh ở SELECT trong WHERE |
Lỗi Invalid column name |
Lặp lại biểu thức gốc, hoặc dùng bí danh ở ORDER BY |
Không có ORDER BY (hoặc thiếu khóa phụ) |
Thứ tự dòng không đảm bảo, chấm sai | ORDER BY luôn có khóa duy nhất ở cuối |
Dùng SELECT * trong báo cáo |
Thừa cột, vỡ khi bảng đổi cấu trúc | Liệt kê đúng các cột cần |
Tóm tắt
| Nội dung | Cú pháp / ghi nhớ |
|---|---|
| Lấy cột từ bảng | SELECT cột FROM bảng (tránh * trong báo cáo) |
| Bí danh, loại trùng | cột AS tên_mới, SELECT DISTINCT ... |
| Thứ tự xử lý | FROM → WHERE → SELECT → ORDER BY |
| Lọc dòng | WHERE điều_kiện (chuỗi/ngày trong nháy đơn, số không có dấu phân cách) |
| So sánh | =, <>/!=, <, >, <=, >= |
| Logic | NOT > AND > OR; luôn dùng ngoặc khi trộn |
| Mẫu chuỗi | LIKE '%…%', % nhiều ký tự, _ một ký tự |
| Tập giá trị / khoảng | IN (…), BETWEEN a AND b (gồm hai đầu) |
| NULL | IS NULL, IS NOT NULL; = NULL luôn sai |
| Sắp xếp | ORDER BY c1 ASC/DESC, c2 … – cột trước ưu tiên hơn; thêm khóa phụ |
| Khác biệt hai hệ ở buổi này | Gần như không có; lưu ý tên bảng phân biệt hoa/thường trên MySQL, N'...' cho chuỗi Unicode trong SQL Server |
Gợi ý mở rộng
-
Trong SSMS, mở Object Explorer → Databases → hnx_securities → Tables → Columns để đối chiếu kiểu dữ liệu với sơ đồ ở mục 2; thử
SELECT * FROMtừng bảng để quen dữ liệu. -
Thử "khách hàng Hà Nội có tài khoản số dư > 50 triệu" bằng một cách nhìn trước Buổi 5 (truy vấn con):
SELECT customer_id, full_name
FROM Customers
WHERE city = 'Ha Noi'
AND customer_id IN (SELECT customer_id FROM Accounts WHERE balance > 50000000)
ORDER BY customer_id;Ở Buổi 8, cùng yêu cầu này sẽ được giải bằng JOIN.
Tự đặt câu hỏi nghiệp vụ trên bảng Securities (mã nào thuộc sàn HNX, ngành ngân hàng?) và MarketPrices (những phiên nào giá đóng cửa thấp hơn giá mở cửa?) rồi viết SELECT cho từng câu.
Đọc thêm: cách SQL Server và MySQL xử lý NULL khi sắp xếp, và collation quyết định việc phân biệt hoa/thường như thế nào.
Bài tập thực hành của buổi
Danh sách đầy đủ các bài của buổi (bạn đã gặp từng nhóm ngay trong bài giảng), nộp trên Codelab bằng SQL Server hoặc MySQL: