Giới thiệu SQL
Xem thửTình huống mở đầu
Chị quản lý Bean Corner muốn gửi thiệp cảm ơn cho khách ở Hanoi. Cửa hàng có 12.000 khách, và chị cần danh sách tên cùng email, xếp theo tên từ A đến Z. Có hai cách nhờ người thủ kho. Cách thứ nhất là dặn từng bước: mở ngăn đầu, đọc thẻ khách, nếu thành phố là Hanoi thì chép ra giấy, sang ngăn kế, làm lại 12.000 lần, cuối cùng ngồi sắp xếp tờ giấy. Cách thứ hai là đưa một tờ phiếu bốn dòng nói rõ chị muốn gì, và thủ kho tự lo phần còn lại.
Tờ phiếu bốn dòng ấy chính là một câu SQL. Hãy đoán trước: trong phiếu, thứ chị phải viết ra gồm những phần nào, và có cần nói "mở ngăn nào trước" không? Cuối bài bạn sẽ thấy câu trả lời bằng một lần chạy thật.
Mục tiêu bài học
Sau bài này bạn có thể:
- Giải thích SQL là ngôn ngữ khai báo: mô tả kết quả muốn có, không mô tả các bước.
- Đọc và viết câu
SELECTcó đủ bốn mệnh đềSELECT,FROM,WHERE,ORDER BYtheo đúng thứ tự. - Dự đoán kết quả một câu
SELECTđơn giản trên bảng vài hàng, kể cả khi kết quả rỗng. - Phân biệt ba nhóm lỗi hay gặp nhất: sai ngữ pháp, sai tên bảng hoặc cột, và quên dấu nháy quanh chuỗi.
Bức tranh lớn
Xuyên suốt bài, câu SQL là tờ phiếu gửi thủ kho, DBMS là thủ kho, và bảng customers của Bean Corner là tủ thẻ khách. Phiếu chỉ ghi kết quả mong muốn, thủ kho chọn cách tìm nhanh nhất.
Hình dưới trả lời một câu hỏi: viết SQL khác gì việc tự viết từng bước?
Đang tải sơ đồ…
Cách đọc hình: đường liền đi từ câu hỏi của quản lý, qua tờ phiếu SQL, qua thủ kho phân tích và lập kế hoạch, rồi ra tập kết quả. Ô đỏ nét đứt là cách làm thủ công: bạn tự viết từng bước, và lỗi nằm ở đâu cũng do bạn.
Chỗ ẩn dụ không còn đúng: thủ kho thật hiểu ý ngầm, còn DBMS chỉ hiểu đúng ngữ pháp SQL. Sai một dấu là bị trả phiếu lại.
Khái niệm cốt lõi
Bài này có bốn khái niệm.
1. SQL là ngôn ngữ khai báo
SQL (Structured Query Language) mô tả kết quả bạn muốn: những khách nào, những cột nào, xếp ra sao. Bạn không viết vòng lặp, không chọn thứ tự duyệt hàng. DBMS có bộ phận lập kế hoạch để chọn cách làm.
2. Câu lệnh gồm các mệnh đề
Một câu SELECT ghép từ các mệnh đề (clause), mỗi mệnh đề một vai: SELECT chọn cột, FROM chọn bảng, WHERE chọn hàng, ORDER BY sắp xếp. Thứ tự viết cố định: SELECT, FROM, WHERE, ORDER BY. Mệnh đề nào không cần thì bỏ, nhưng không đổi chỗ.
3. Quy tắc viết
Từ khóa như SELECT không phân biệt hoa thường, nhưng quy ước viết hoa để dễ đọc. Giá trị chuỗi nằm trong dấu nháy đơn, như 'Hanoi'. Tên bảng và tên cột viết không nháy. Câu lệnh kết thúc bằng dấu chấm phẩy. Xuống dòng và khoảng trắng thừa không có ý nghĩa.
4. Kết quả là một tập hàng
Kết quả SELECT là result set: một tập hàng có cột cố định. Tập hàng này không có thứ tự bảo đảm trừ khi bạn viết ORDER BY, và nó có thể rỗng.
Ví dụ đầu tiên
Bảng customers của Bean Corner có sáu hàng với các cột id, name, email, city. Ví dụ nhỏ nhất là chính tờ phiếu của chị quản lý:
SELECT name, email
FROM customers
WHERE city = 'Hanoi'
ORDER BY name;
Dự đoán trước khi xem kết quả: kết quả có mấy hàng, mấy cột, hàng đầu tiên là ai?
Kết quả [Đã chạy: MySQL 8.0.44]:
+-----------+------------------+
| name | email |
+-----------+------------------+
| An Nguyen | an@bean.example |
| Chi Tran | chi@bean.example |
| Em Vo | em@bean.example |
+-----------+------------------+
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
SELECT name, email | Chọn hai cột name và email để đưa vào kết quả | Chỉ lấy cột cần dùng, bỏ id và city |
FROM customers | Lấy dữ liệu từ bảng customers | FROM luôn cho biết nguồn dữ liệu |
WHERE city = 'Hanoi' | Giữ các hàng có city bằng chuỗi Hanoi | Chuỗi cần dấu nháy đơn, nếu không MySQL tưởng là tên cột |
ORDER BY name; | Sắp xếp theo name tăng dần rồi kết thúc câu | Không có ORDER BY thì thứ tự hàng không được bảo đảm |
Ba khách ở Hanoi, hai cột, hàng đầu là An Nguyen: đúng như tờ phiếu mô tả, không có dòng nào nói "mở ngăn nào trước".
Xem từng bước
Animation dưới đây cho thấy kết quả được thu hẹp dần qua từng mệnh đề. Hãy trả lời câu dự đoán trước khi bấm bước đầu tiên.
Đang tải Animation…
Bảng trace bằng chữ [Đã chạy: MySQL 8.0.44, bảng customers sáu hàng]:
| Mệnh đề | Việc làm | Số hàng còn lại | Số cột còn lại |
|---|---|---|---|
FROM customers | Lấy cả bảng | 6 | 4 |
WHERE city = 'Hanoi' | Giữ Chi Tran, Em Vo, An Nguyen | 3 | 4 |
SELECT name, email | Giữ hai cột | 3 | 2 |
ORDER BY name | An Nguyen, Chi Tran, Em Vo | 3 | 2 |
Bạn vừa làm được: đọc một câu SELECT theo từng mệnh đề và nói chính xác nó giữ lại những hàng nào.
Concept Check
Câu 1. SQL là ngôn ngữ khai báo. Câu nào mô tả đúng điều đó?
- A. Bạn viết vòng lặp qua từng hàng để DBMS biết thứ tự duyệt.
- B. Bạn chỉ cần mô tả kết quả mong muốn, còn cách tìm do DBMS chọn.
- C. Bạn phải khai báo trước kiểu dữ liệu của kết quả ở dòng đầu.
- D. Bạn phải báo cho DBMS số hàng sẽ trả về trước khi chạy.
Câu 2. Câu nào chạy được?
- A.
SELECT name FROM customers ORDER BY name WHERE city = 'Hanoi'; - B.
FROM customers SELECT name WHERE city = 'Hanoi'; - C.
SELECT name FROM customers WHERE city = 'Hanoi' ORDER BY name; - D.
WHERE city = 'Hanoi' SELECT name FROM customers;
Câu 3. SELECT name FROM customers WHERE city = 'Paris'; trên bảng không có khách nào ở Paris. Máy khách mysql in ra gì?
- A. Một dòng
ERRORvì không tìm thấy khách nào. - B. Một bảng có một hàng toàn giá trị
NULL. - C. Không in gì cả, vì tập kết quả rỗng.
- D. Toàn bộ bảng, vì điều kiện không khớp nên bị bỏ qua.
Đáp án và giải thích:
- Câu 1: B. Khai báo nghĩa là mô tả kết quả. A mô tả lập trình thủ tục. C và D là những thứ SQL không yêu cầu.
- Câu 2: C. Thứ tự viết cố định là
SELECT,FROM,WHERE,ORDER BY. A đặtORDER BYtrướcWHERE, B và D đảo mệnh đề đầu. - Câu 3: C. Rỗng không phải lỗi, máy khách chỉ không in bảng nào. A sai vì không có lỗi. B và D sai vì DBMS không bịa hàng và không bỏ điều kiện.
Làm cùng mình
Chị quản lý đổi ý: lần này cần khách ở Da Nang, xếp theo tên từ Z về A, kèm email.
Bước 1. Viết bằng lời trước
Chia thành bốn dòng giấy: lấy cột nào, từ bảng nào, giữ hàng nào, xếp thế nào. Mỗi dòng ứng với đúng một mệnh đề.
Bước 2. Hoàn thành câu lệnh
-- TODO 1: chọn hai cột name và email
FROM customers
-- TODO 2: chỉ giữ khách có city là Da Nang
-- TODO 3: xếp theo name giảm dần (từ Z về A)
;
Gợi ý: chuỗi có khoảng trắng như 'Da Nang' vẫn chỉ cần một cặp nháy đơn. Chiều giảm dần dùng từ khóa DESC đặt sau tên cột.
Bước 3. Dự đoán trước khi chạy
Trong sáu khách, ai ở Da Nang? Sau khi xếp Z về A, ai đứng đầu? Ghi ra giấy rồi mới đối chiếu với mục Lời giải và đáp án.
Bước 4. Vẽ lại
Vẽ lại bảng kết quả trên giấy và ghi một câu: nếu bỏ ORDER BY, vì sao bạn không thể khẳng định ai đứng đầu?
Thử phá nó đi
Bốn lỗi người mới thường gặp, cùng một điểm chung: lỗi do viết sai chứ không do dữ liệu [Đã chạy: MySQL 8.0.44].
| Code lỗi | Thông báo lỗi | Nguyên nhân | Cách sửa |
|---|---|---|---|
SELECT name FROM customers ORDER BY name WHERE city = 'Hanoi'; | ERROR 1064 (42000): You have an error in your SQL syntax ... near 'WHERE city = 'Hanoi'' | WHERE đứng sau ORDER BY sai thứ tự mệnh đề | Đưa WHERE lên trước ORDER BY |
SELECT name FROM customers WHERE city = Hanoi; | ERROR 1054 (42S22): Unknown column 'Hanoi' in 'where clause' | Thiếu dấu nháy nên Hanoi bị hiểu là tên cột | Viết 'Hanoi' |
SELECT name FROM client; | ERROR 1146 (42S02): Table 'shop.client' doesn't exist | Gõ nhầm tên bảng | Sửa thành customers |
SELECT nme FROM customers WHERE city = 'Hanoi'; | ERROR 1054 (42S22): Unknown column 'nme' in 'field list' | Gõ nhầm tên cột | Sửa thành name |
Có một lỗi không báo lỗi gì cả, nguy hiểm hơn: quên dấu phẩy giữa hai cột.
SELECT name city FROM customers LIMIT 2;
+----------+
| city |
+----------+
| Chi Tran |
| Binh Le |
+----------+
Bạn tưởng đang chọn hai cột name và city, nhưng MySQL hiểu city là bí danh đặt cho cột name. Kết quả chỉ có một cột tên city chứa tên khách. Vì vậy sau khi chạy, luôn đối chiếu số cột và tên cột của kết quả với ý bạn muốn.
Đang tải sơ đồ…
Cách đọc hình: đi từ trên xuống, mỗi ô cam là một cổng kiểm tra của DBMS. Hai ô đỏ là hai cổng đầu: sai ngữ pháp, sai tên. Cổng cuối là kết quả rỗng, không phải lỗi nên không có ô đỏ. Lỗi quên dấu phẩy ở trên đi lọt cả ba cổng vì câu lệnh vẫn đúng ngữ pháp, chỉ khác ý bạn muốn.
Tự tay làm
Dùng chung bảng customers sáu hàng của bài.
Cơ bản
Bài 1. Dự đoán kết quả. Đầu vào: câu lệnh sau.
SELECT name
FROM customers
WHERE city = 'Hue' OR city = 'Da Nang'
ORDER BY name;
Ghi ra các tên khách theo đúng thứ tự dòng. Tiêu chí đạt: đúng số hàng và đúng thứ tự. Gợi ý: OR nghĩa là chỉ cần thỏa một trong hai điều kiện.
Vận dụng
Bài 2. Sửa ba câu sai. Đầu vào: ba câu dưới, mỗi câu sai một chỗ. Với mỗi câu, nói lỗi nằm ở mệnh đề nào, rồi viết lại cho đúng.
SELECT name email FROM customers WHERE city = 'Hue';
SELECT email, name FROM customers ORDER BY email DESC WHERE city = 'Hanoi';
SELECT name FROM customers WHERE city = Da Nang;
Tiêu chí đạt: sửa đúng cả ba. Gợi ý: một câu chạy được nhưng cho kết quả khác ý, một câu sai thứ tự mệnh đề, một câu sai về chuỗi.
Thử thách (tùy chọn)
Bài 3. Phiếu cho thủ kho. Đầu vào: yêu cầu "email của khách ở Hanoi, xếp email từ Z về A, chỉ lấy một cột". Viết câu lệnh, rồi giải thích bằng một câu vì sao lệnh này không cần biết bảng có bao nhiêu hàng. Tiêu chí đạt: kết quả là một cột email, đúng thứ tự, và câu giải thích nhắc đến việc SQL khai báo kết quả mong muốn.
Tự kiểm tra
Trả lời bằng lời của bạn, không nhìn lại bài.
- (Mục tiêu 1, nhớ lại) Khai báo khác thủ tục ở điểm nào? Cho một ví dụ trong Bean Corner.
- (Mục tiêu 2, dự đoán) Thứ tự viết đúng của bốn mệnh đề là gì, và mệnh đề nào có thể bỏ?
- (Mục tiêu 3, dự đoán) Truy vấn
WHERE city = 'hanoi'(chữ thường) trong MySQL của bài trả về mấy hàng? - (Mục tiêu 3, dự đoán) Truy vấn không có
ORDER BYcó bảo đảm thứ tự hàng không? - (Mục tiêu 4, tìm lỗi)
WHERE city = Hanoibáo lỗi gì, vì sao, và sửa thế nào?
Điều cần nhớ
- SQL mô tả kết quả muốn có; DBMS chọn cách tìm.
- Câu
SELECTgồm các mệnh đềSELECT,FROM,WHERE,ORDER BYtheo đúng thứ tự viết. - Chuỗi dùng dấu nháy đơn; tên bảng và cột không dùng nháy; từ khóa không phân biệt hoa thường.
- Kết quả là tập hàng, có thể rỗng, và chỉ có thứ tự khi có
ORDER BY.
Cheat sheet:
| Muốn | Viết | Ghi chú |
|---|---|---|
| Chọn cột | SELECT name, email | Các cột ngăn bằng dấu phẩy |
| Chọn bảng | FROM customers | Bắt buộc khi lấy dữ liệu từ bảng |
| Lọc hàng | WHERE city = 'Hanoi' | Chuỗi trong nháy đơn |
| Sắp xếp | ORDER BY name DESC | Mặc định tăng dần, DESC là giảm dần |
| Kết thúc câu | ; | Cần khi gõ nhiều câu liên tiếp |
Hình tóm tắt của bài là sơ đồ ở mục Bức tranh lớn.
Câu hỏi để dành cho bài sau: cột name là chuỗi, cột id là số. Nếu bạn lưu giá tiền dưới dạng chuỗi hoặc dạng số thực thì có khác gì khi cộng dồn?
Đào sâu
Phần này dành cho bạn tò mò.
- Cơ chế bên trong. Mỗi câu lệnh đi qua hai giai đoạn: phân tích cú pháp (đúng ngữ pháp không, tên có tồn tại không) rồi lập kế hoạch thực thi. Lỗi 1064 xảy ra ở giai đoạn một, trước cả khi chạm vào dữ liệu. Bạn sẽ gặp lại kế hoạch thực thi khi học về chỉ mục.
- So sánh với lựa chọn khác. Chữ hoa chữ thường trong giá trị chuỗi phụ thuộc DBMS và collation. Trong MySQL của bài,
'hanoi'khớp'Hanoi':
SELECT name FROM customers WHERE city = 'hanoi';
+-----------+
| name |
+-----------+
| Chi Tran |
| Em Vo |
| An Nguyen |
+-----------+
Cùng truy vấn trên PostgreSQL 16 cho kết quả khác [Đã chạy: PostgreSQL 16.15]:
name
------
(0 rows)
Dấu nháy kép cũng khác: MySQL mặc định chấp nhận "Da Nang" như chuỗi, còn PostgreSQL coi nó là tên cột [Đã chạy: PostgreSQL 16.15]:
ERROR: column "Da Nang" does not exist
LINE 1: SELECT name FROM customers WHERE city = "Da Nang";
^
- Hiểu lầm nâng cao. Nhiều người nghĩ không có
ORDER BYthì kết quả luôn theo thứ tự thêm vào. Thứ tự đó thường xảy ra trên bảng nhỏ nhưng không được bảo đảm [Có thể khác nhau]: DBMS được quyền trả hàng theo bất kỳ thứ tự nào hiệu quả nhất. - Giới hạn của bài. Bài này chưa nói đến chọn cột bằng biểu thức,
DISTINCT,LIMIT,JOINhay hàm tổng hợp. Mỗi chủ đề có bài riêng. - Đọc thêm. MySQL 8.0 Reference Manual, mục SELECT Statement: https://dev.mysql.com/doc/refman/8.0/en/select.html
Bài tiếp theo
Bài sau là Kiểu dữ liệu: vì sao tiền không nên lưu bằng số thực, NULL khác chuỗi rỗng ở đâu, và MySQL từ chối hay cắt dữ liệu khi vượt giới hạn cột. Mini-dự án nối tiếp: thiết kế kiểu cho các cột của bảng products của Bean Corner.
Lời giải và đáp án
Làm cùng mình, Bước 2. Câu lệnh đầy đủ:
SELECT name, email
FROM customers
WHERE city = 'Da Nang'
ORDER BY name DESC;
+----------+--------------------+
| name | email |
+----------+--------------------+
| Giang Do | giang@bean.example |
| Binh Le | binh@bean.example |
+----------+--------------------+
Hai khách ở Da Nang là Binh Le và Giang Do. Xếp Z về A nên Giang Do đứng trước. Nếu bỏ ORDER BY, thứ tự không được bảo đảm: bạn chỉ có thể nói đây là hai hàng, không nói ai đứng đầu.
Bài 1 [Đã chạy: MySQL 8.0.44]:
+-----------+
| name |
+-----------+
| Binh Le |
| Dung Pham |
| Giang Do |
+-----------+
Ba hàng: khách ở Hue là Dung Pham, khách ở Da Nang là Binh Le và Giang Do, xếp theo tên.
Bài 2.
SELECT name email FROM customers WHERE city = 'Hue';thiếu dấu phẩy nênemailthành bí danh củaname. Sửa:SELECT name, email FROM customers WHERE city = 'Hue';
+-----------+-------------------+
| name | email |
+-----------+-------------------+
| Dung Pham | dung@bean.example |
+-----------+-------------------+
WHEREphải đứng trướcORDER BY. Sửa:
SELECT email, name
FROM customers
WHERE city = 'Hanoi'
ORDER BY email DESC;
+------------------+-----------+
| email | name |
+------------------+-----------+
| em@bean.example | Em Vo |
| chi@bean.example | Chi Tran |
| an@bean.example | An Nguyen |
+------------------+-----------+
'Da Nang'cần nháy đơn, nếu không MySQL thấy hai từ lạ. Sửa:WHERE city = 'Da Nang'.
Bài 3.
SELECT email
FROM customers
WHERE city = 'Hanoi'
ORDER BY email DESC;
Lệnh không cần biết số hàng vì nó chỉ nói kết quả mong muốn: những email nào và xếp ra sao. DBMS tự duyệt bảng bao nhiêu hàng cũng được.
Tự kiểm tra.
- Khai báo là mô tả kết quả muốn có, như "khách ở Hanoi xếp theo tên", còn thủ tục là liệt kê từng bước duyệt và so sánh.
SELECT,FROM,WHERE,ORDER BY.WHEREvàORDER BYcó thể bỏ;SELECTvàFROMcần có khi lấy dữ liệu từ bảng.- Ba hàng, vì collation mặc định của MySQL 8.0 không phân biệt hoa thường, nên
'hanoi'khớp'Hanoi'. - Không. Chỉ
ORDER BYbảo đảm thứ tự. ERROR 1054 Unknown column 'Hanoi', vì thiếu dấu nháy nên MySQL coiHanoilà tên cột. Sửa thành'Hanoi'.