Ở bài trước, bạn đã có tấm bản đồ của cơ sở dữ liệu: bảng, hàng, cột, khóa chính, khóa ngoại. Nhưng bản đồ để làm gì nếu cứ treo trên tường? Hôm nay bạn sẽ nói câu đầu tiên với database, và nhận về đúng dữ liệu mình muốn.
Nhớ lại câu chuyện: database là một thư viện khổng lồ có ông thủ thư (DBMS) chứ. Bạn không tự bới kệ. Thay vào đó, bạn đưa cho thủ thư một tờ phiếu yêu cầu: “Cho tôi xin tên và ngày xuất bản của các cuốn sách”. Tờ phiếu ấy trong SQL có tên là SELECT.
Tin vui: SELECT là câu lệnh bạn sẽ gõ nhiều nhất trong quá trình làm việc với dữ liệu và nó dễ đến bất ngờ. Chỉ trong bài này, bạn đã có thể trả lời những câu như “ba cuốn sách mới nhất là cuốn nào?” chỉ bằng một dòng.
Khác với bài trước, từ đây bạn sẽ thực hành trực tiếp trên SQL playground ngay trong trang. Cứ mạnh dạn gõ và chạy thử, sai thì chạy lại, không hỏng gì cả.
Vẫn là bảng sinh_vien quen thuộc từ bài trước, lần này có thêm cột diem_tb (điểm trung bình) để ta có cái mà sắp xếp. Tổng cộng 6 sinh viên thuộc 3 lớp, dưới đây là 3 dòng đầu:
| ma_sv | ho_ten | ngay_sinh | gioi_tinh | lop | diem_tb |
|---|---|---|---|---|---|
| SV001 | Nguyễn Văn An | 2005-03-12 | Nam | CNTT01 | 8.5 |
| SV002 | Trần Thị Bình | 2005-07-25 | Nữ | CNTT01 | 7.0 |
| SV003 | Lê Hoàng Cường | 2004-11-02 | Nam | CNTT02 | 9.2 |
Đây là con đường ta sẽ đi trong bài này:
Mọi câu lệnh lấy dữ liệu đều bắt đầu bằng đúng hai từ khóa: SELECT và FROM. Công thức tối giản đến mức có thể đọc thành một câu tiếng Việt:
SELECT những cột bạn muốn FROM bảng chứa chúng;
FROM trả lời câu hỏi “lấy từ bảng nào?”, còn SELECT trả lời “lấy những cột nào?”. Dấu chấm phẩy ; ở cuối đánh dấu kết thúc một câu lệnh.
Bạn có hai cách nói với database mình cần cột nào. Dấu *nghĩa là “cho tôi hết”, còn liệt kê tên cột nghĩa là “chỉ đúng mấy cột này thôi”. Cách thứ hai còn quyết định luôn thứ tự các cột hiển thị: bạn viết cột nào trước thì nó nằm bên trái, bất kể trong bảng nó nằm ở đâu.
Khi viết ứng dụng thật, hãy ưu tiên liệt kê tên cột thay cho *. Câu lệnh sẽ nói rõ bạn cần gì, tránh vô tình kéo về những cột nặng (ảnh, mô tả dài) mà bạn không dùng đến.
Bạn vừa thấy bước cuối của animation trên: tiêu đề cột đổi từ ho_ten thành Họ và tên. Thủ phạm là từ khóa AS, thứ đặt cho cột một tên hiển thị (alias) trong bảng kết quả.
Tên cột trong database thường viết không dấu, dính liền để dễ gõ, nhưng khi xuất báo cáo cho người khác đọc thì diem_tb trông khô khan hơn hẳn “Điểm trung bình”. Nếu tên mới có dấu cách hoặc tiếng Việt, hãy bọc nó trong nháy kép.
Điều quan trọng nhất cần nhớ: AS chỉ đổi nhãn ở kết quả trả về. Cột trong bảng vẫn tên ho_ten, dữ liệu không thay đổi một ký tự. Chạy lại câu SELECT * FROM sinh_vien là bạn thấy mọi thứ y như cũ.
Vậy là bạn đã lấy được cột mình cần. Nhưng thử hỏi database một câu rất đời thường xem: “trường đang có những lớp nào?”. Viết SELECT lop FROM sinh_vien thì kết quả trả về sáu dòng, trong đó CNTT01 lặp lại tận ba lần, còn thứ tự thì lộn xộn. Rõ ràng SELECT và FROM vẫn còn thiếu vài người bạn.
Người bạn thứ nhất giải quyết chuyện lặp lại. Sáu dòng lop vừa rồi bị trùng là chuyện đương nhiên, vì bảng lưu mỗi sinh viên một dòng chứ đâu lưu mỗi lớp một dòng. Nhưng câu hỏi của bạn không phải “có bao nhiêu sinh viên”, mà là có bao nhiêu giá trị khác nhau.
DISTINCT gộp các dòng trùng nhau lại, giữ mỗi giá trị đúng một lần.
Đây là hiểu lầm phổ biến nhất về DISTINCT. Nó không đứng riêng với cột ngay sau nó, mà xét toàn bộ tổ hợp các cột bạn liệt kê: hai dòng chỉ bị coi là trùng khi giống nhau ở mọi cột. Với bộ dữ liệu của bài này, SELECT DISTINCT lop cho 3 dòng, nhưng thêm một cột nữa thành SELECT DISTINCT lop, gioi_tinhthì ra tới 5 dòng, vì giờ mỗi cặp lớp-giới tính mới là một “giá trị”. Bạn sẽ tự tay chạy thử ở phần thực hành.
Một điều nhiều người mới hay bất ngờ: mặc định, thứ tự các dòng mà database trả về là không đảm bảo. Muốn có thứ tự chắc chắn, bạn phải nói ra bằng ORDER BY.
Thêm ASC để sắp tăng dần (đây cũng là mặc định nếu bạn không ghi gì), hoặc DESC để sắp giảm dần.
| Kiểu dữ liệu | ASC (tăng dần) | DESC (giảm dần) |
|---|---|---|
| Số (điểm) | 0 → 10 (thấp đến cao) | 10 → 0 (cao đến thấp) |
| Chữ (họ tên) | A → Z | Z → A |
| Ngày (ngay_sinh) | Cũ nhất → mới nhất | Mới nhất → cũ nhất (trẻ nhất trước) |
Bạn còn có thể đưa cho ORDER BY nhiều cột, cách nhau bằng dấu phẩy. Database sắp theo cột đầu tiên trước; chỉ khi nào hai dòng bằng nhau ở cột đó nó mới lấy cột thứ hai ra phân xử. Mỗi cột lại có chiều riêng của nó, nên chuyện sắp cột này tăng còn cột kia giảm là hoàn toàn bình thường.
Kết quả đọc rất đã mắt: ba bạn lớp CNTT01 nằm liền một khối và trong khối đó điểm giảm dần (8.5 → 8.0 → 7.0), rồi mới tới khối CNTT02, cuối cùng là CNTT03. Đây chính là dạng bảng xếp hạng theo nhóm mà bạn gặp suốt ngày ngoài đời.
Bảng thật có thể có hàng triệu dòng nhưng hiếm khi bạn muốn xem hết. Vậy nên ta dùng LIMIT n giữ lại đúng n dòng đầu của kết quả.
Ghép ORDER BY với LIMIT sẽ tạo ra công thức kinh điển để lấy “top N”: 3 bạn trẻ nhất, 5 điểm cao nhất, 10 đơn hàng mới nhất… Sắp xếp trước, rồi cắt lấy phần đầu.
LIMIT chỉ biết cắt lấy n dòng đầu của kết quả, nó hoàn toàn không biết “giỏi nhất” hay “mới nhất” nghĩa là gì. Viết SELECT ho_ten, diem_tb FROM sinh_vien LIMIT 3 là bạn nhận về ba dòng bất kỳ mà database thấy tiện lấy trước, không phải ba bạn điểm cao nhất. Muốn có nghĩa thì phải xếp hạng trước đã: ORDER BY làm việc chọn ai đứng đầu, LIMIT chỉ làm mỗi việc cầm kéo.
Các mảnh ghép luôn xuất hiện theo đúng thứ tự này: SELECT → FROM → ORDER BY → LIMIT. Viết sai thứ tự (ví dụ đặt LIMIT trước ORDER BY) là database sẽ báo lỗi ngay.
Mười bài tăng dần độ khó trên bảng sinh_vien, chạy thật chứ không phải kết quả dựng sẵn. Đọc yêu cầu, gõ (hoặc chỉnh) câu lệnh rồi bấm chạy để xem kết quả. Ba câu bẫy nằm rải ở câu 4, 9 và 10: chúng cố tình cho ra kết quả sai hoặc báo lỗi, và việc của bạn là sửa lại cho đúng.
SQL Playground chỉ mở cho học viên đã mua khoá “SQL Cơ Bản”. Khi mua, bạn được chạy trực tiếp query trên PostgreSQL với dataset thật của bài học.
Ở câu 8, đổi LIMIT 3 thành LIMIT 1 để tìm bạn trẻ nhất, hay đổi DESC thành ASC để tìm bạn lớn tuổi nhất. Ở câu 4, thử thêm tiếp cột ngay_sinh vào sau DISTINCT: mỗi sinh viên có một ngày sinh riêng nên chẳng còn cặp nào trùng, kết quả nở lại đủ 6 dòng. Càng thêm cột thì DISTINCT càng khó gộp. Sai thì chạy lại, không hỏng gì cả.
Chỉ với vài từ khóa, bạn đã có thể “trò chuyện” với database đủ kiểu. Đây là tấm cheat sheet bỏ túi để xem lại bất cứ lúc nào:
SELECTChọn CỘT muốn lấy raFROMChỉ ra BẢNG chứa dữ liệu*Lấy hết mọi cột, tiện xem nhanhtên cộtLiệt kê đúng cột cần: gọn, nhẹ, rõASĐổi tên hiển thị của cột (alias)DISTINCTLoại dòng trùng, xét trên CẢ BỘ cột liệt kêORDER BYSắp xếp tăng (ASC) hoặc giảm (DESC)ORDER BY a, bBằng nhau ở cột a thì mới xét tới cột bLIMITGiữ lại n dòng đầu tiênGhép ORDER BY với LIMITlà bạn trả lời được vô số câu hỏi “top N”: 3 bạn trẻ nhất, 5 điểm cao nhất, 10 đơn mới nhất… Bí quyết luôn giống nhau: sắp xếp trước, rồi cắt lấy phần đầu.
Nói thì dễ, ghép mới thấm. Dưới đây là một câu duy nhất gom lại gần như mọi từ khóa bạn vừa học. Kéo từng mảnh vào chỗ trống, điền sai cũng không sao vì database sẽ trả lời bạn y như thật.
Có một điều đáng để ý: suốt cả bài, mọi câu lệnh của bạn đều trả về toàn bộ sinh viên trong bảng. SELECT chọn cột, ORDER BY đổi thứ tự, LIMITcắt bớt phần đuôi, nhưng chưa có công cụ nào giúp bạn nói “chỉ lấy những bạn thỏa điều kiện này”. Đó chính xác là chỗ trống mà WHERE của bài sau lấp vào.
Nhấp vào từng thẻ để lật và tự kiểm tra trước khi làm quiz.
SELECT … FROM … dùng để làm gì?
Lấy dữ liệu ra khỏi database: SELECT nói lấy CỘT nào, FROM nói lấy từ BẢNG nào. Ví dụ: SELECT ho_ten FROM sinh_vien.