Trong bài này
Cô Mai nhận một danh sách có tên bị thừa khoảng trắng và muốn thêm lời xưng hô trước mỗi tên. Thầy Nam lại cần chia các lần chấm bài theo tháng để theo dõi hoạt động. Dữ liệu đã có nhưng cách hiển thị chưa thuận tiện cho người đọc. Bạn có thể tạo kết quả hiển thị tên gọn hơn và thêm nhãn tháng ngay trong truy vấn. Những phép tính này vẫn giữ nguyên dữ liệu đã lưu trong bảng gốc.
TRIM làm gọn hai đầu của tên
Chuỗi (string) là một dãy ký tự, chẳng hạn tên học viên hoặc nhãn trong báo cáo. Khi gõ giá trị chuỗi trực tiếp trong SQL, bạn đặt nó trong dấu nháy đơn. Các hàm chuỗi tạo ra giá trị mới để đưa vào kết quả.
Hàm TRIM mặc định bỏ ký tự khoảng trắng ở đầu và cuối chuỗi. Để nhìn thấy tác dụng, mục này dùng dữ liệu thử có tên của An bị thêm khoảng trắng. Bản dữ liệu thử này chỉ dùng trong ví dụ ghi setup=ten-co-khoang. Bảng học viên chung vẫn giữ nguyên tên đã lưu.
Trước tiên, hãy so sánh tên nguồn với tên làm gọn trên đúng một học viên. Các dấu cách trong cột nguồn là phần dữ liệu cần xử lý:
Dữ liệu đầu vào
studentsgiữ 1/6 dòng| id | name |
|---|---|
| 1 | An |
| 2 | Bình |
| 3 | Chi |
| 4 | Dung |
| 5 | Huy |
| 6 | Lan |
Câu lệnh
SELECT id, name, TRIM(name) AS clean_name
FROM students
WHERE id = 1;Kết quả
| id | name | clean_name |
|---|---|---|
| 1 | An | An |
Cột clean_name chứa An, đã bỏ các dấu cách ở hai đầu. Cột name vẫn chứa chuỗi ban đầu. Hàm chỉ tính tên để hiển thị trong kết quả. Muốn thay tên đã lưu, bạn phải dùng lệnh cập nhật riêng.
TRIM chỉ làm gọn hai đầu chuỗi, chứ không xóa mọi khoảng trắng trong tên. Khoảng trắng giữa các phần tên vẫn được giữ, kể cả khi người nhập gõ hai dấu cách. Hàm LENGTH đếm số ký tự của văn bản, giúp kiểm điều này. Hãy chạy thử trên một chuỗi ghi trực tiếp trong câu lệnh:
Câu lệnh
SELECT TRIM(' An Bình ') AS clean_name,
LENGTH(TRIM(' An Bình ')) AS characters;Kết quả
| clean_name | characters |
|---|---|
| An Bình | 8 |
Kết quả còn hai dấu cách giữa An và Bình. characters bằng 8 vì phần bên trong không bị sửa. Nếu muốn các từ chỉ cách nhau một dấu cách, bạn cần cách xử lý riêng. Chỉ dùng TRIM sẽ chưa sửa được khoảng trắng thừa ở giữa tên.
LENGTH đếm ký tự, SUBSTR lấy một đoạn
Hàm LENGTH cho biết số ký tự của một giá trị văn bản thông thường. Hàm SUBSTR lấy một đoạn chuỗi theo vị trí bắt đầu và số ký tự cần lấy. Trong cách dùng ở đây, vị trí 1 là ký tự đầu tiên.
Hãy xem tên học viên cùng độ dài của tên. Cột số mới được tính trên từng dòng, nên số dòng kết quả vẫn bằng số học viên:
Dữ liệu đầu vào
students6 dòng| id | name |
|---|---|
| 1 | An |
| 2 | Bình |
| 3 | Chi |
| 4 | Dung |
| 5 | Huy |
| 6 | Lan |
Câu lệnh
SELECT id, name, LENGTH(name) AS characters
FROM students
ORDER BY id;Kết quả
| id | name | characters |
|---|---|---|
| 1 | An | 2 |
| 2 | Bình | 4 |
| 3 | Chi | 3 |
| 4 | Dung | 4 |
| 5 | Huy | 3 |
| 6 | Lan | 3 |
Tên Bình gồm 4 ký tự. Hàm đếm ký tự của tên, nên chữ có dấu không làm kết quả tăng theo số byte lưu trữ. Tên An có 2 ký tự, còn Dung có 4. Kết quả phụ thuộc đúng chuỗi đang lưu, nên tên còn khoảng trắng cũng làm số ký tự tăng.
Để lấy một phần tên lớp, đặt vị trí bắt đầu và độ dài sau tên cột. Câu sau lấy bốn ký tự đầu của mỗi tên trong bảng classes:
Dữ liệu đầu vào
classes3 dòng| id | name |
|---|---|
| 1 | Python |
| 2 | Scratch |
| 3 | Database |
Câu lệnh
SELECT id, name, SUBSTR(name, 1, 4) AS prefix
FROM classes
ORDER BY id;Kết quả
| id | name | prefix |
|---|---|---|
| 1 | Python | Pyth |
| 2 | Scratch | Scra |
| 3 | Database | Data |
Tên Database cho đoạn đầu Data, tên Python cho Pyth, còn tên Scratch cho Scra. Độ dài 4 là số ký tự cần lấy, không phải vị trí kết thúc. Bạn có thể đối chiếu ký tự đầu để phát hiện lỗi bắt đầu từ vị trí 2.
Nối chuỗi để tạo nhãn rõ nghĩa
Nối chuỗi (concatenation) là ghép các đoạn chữ thành một chuỗi mới. Trong SQLite, toán tử || thực hiện việc nối. Bạn cần tự ghi khoảng trắng hoặc dấu phân cách nếu muốn chúng xuất hiện trong nhãn.
Cô Mai muốn thêm lời xưng hô Bạn trước tên mỗi học viên. Bạn cần đặt dấu cách sau lời xưng hô, bên trong chuỗi 'Bạn ':
Dữ liệu đầu vào
students6 dòng| id | name |
|---|---|
| 1 | An |
| 2 | Bình |
| 3 | Chi |
| 4 | Dung |
| 5 | Huy |
| 6 | Lan |
Câu lệnh
SELECT id, 'Bạn ' || name AS label
FROM students
ORDER BY id;Kết quả
| id | label |
|---|---|
| 1 | Bạn An |
| 2 | Bạn Bình |
| 3 | Bạn Chi |
| 4 | Bạn Dung |
| 5 | Bạn Huy |
| 6 | Bạn Lan |
Dòng của An có nhãn Bạn An, còn Bình có Bạn Bình. Nếu viết 'Bạn' || name, hai phần sẽ dính thành BạnAn. Toán tử nối giữ những ký tự được cung cấp, không tự đoán dấu cách cần chèn.
Khi một thành phần là NULL, phép nối bằng || cho kết quả NULL. Một số học viên chưa có người hướng dẫn, nên cột mentor có thể thiếu giá trị. Bạn cần chọn một nhãn để người đọc nhận ra trường hợp này. Hãy giữ tên học viên và thay riêng phần chưa có giá trị:
Dữ liệu đầu vào
students6 dòng| id | name | mentor |
|---|---|---|
| 1 | An | Cô Mai |
| 2 | Bình | NULL |
| 3 | Chi | Thầy Nam |
| 4 | Dung | NULL |
| 5 | Huy | Cô Mai |
| 6 | Lan | Thầy Nam |
Câu lệnh
SELECT id, name || ' — '
|| COALESCE(mentor, 'Chưa phân công') AS label
FROM students
ORDER BY id;Kết quả
| id | label |
|---|---|
| 1 | An — Cô Mai |
| 2 | Bình — Chưa phân công |
| 3 | Chi — Thầy Nam |
| 4 | Dung — Chưa phân công |
| 5 | Huy — Cô Mai |
| 6 | Lan — Thầy Nam |
Nhãn của Bình và Dung vẫn giữ tên hai bạn, kèm phần ghi chú Chưa phân công. Các bạn khác nhận tên người hướng dẫn đang lưu. Nếu thay NULL bằng chuỗi rỗng, người đọc sẽ khó biết đó là dữ liệu thiếu hay chủ ý không ghi.
Lọc ngày bằng một khoảng có quy ước
Bảng scores lưu ngày chấm trong cột day theo dạng YYYY-MM-DD. Bạn viết năm trước, rồi đến tháng và ngày. Năm có bốn chữ số, còn tháng và ngày đều có hai chữ số. Với các ngày hợp lệ cùng dạng ấy, thứ tự chuỗi tương ứng thứ tự thời gian.
Khoảng nửa mở (half-open interval) là khoảng bao gồm giá trị ở đầu, nhưng không bao gồm giá trị ở cuối. Để lọc tháng 9, lấy từ ngày đầu tháng 9 đến trước ngày đầu tháng 10. Cách viết này tránh phải đoán ngày cuối cùng của từng tháng.
Hãy kiểm các lần chấm trong tháng 9 bằng hai phép so sánh. Điều kiện thứ hai dùng dấu nhỏ hơn, không dùng nhỏ hơn hoặc bằng:
Dữ liệu đầu vào
scoresgiữ 7/7 dòng| id | student_id | score | day |
|---|---|---|---|
| 1 | 1 | 80 | 2026-09-01 |
| 2 | 1 | 90 | 2026-09-02 |
| 3 | 2 | 70 | 2026-09-01 |
| 4 | 3 | 90 | 2026-09-01 |
| 5 | 3 | 100 | 2026-09-02 |
| 6 | 4 | 50 | 2026-09-01 |
| 7 | 5 | 90 | 2026-09-01 |
Câu lệnh
SELECT id, student_id, score, day
FROM scores
WHERE day >= '2026-09-01' AND day < '2026-10-01'
ORDER BY id;Kết quả
| id | student_id | score | day |
|---|---|---|---|
| 1 | 1 | 80 | 2026-09-01 |
| 2 | 1 | 90 | 2026-09-02 |
| 3 | 2 | 70 | 2026-09-01 |
| 4 | 3 | 90 | 2026-09-01 |
| 5 | 3 | 100 | 2026-09-02 |
| 6 | 4 | 50 | 2026-09-01 |
| 7 | 5 | 90 | 2026-09-01 |
Cả bảy dòng được giữ vì dữ liệu mẫu chỉ có ngày tháng 9. Để thấy bộ lọc loại ngày nào, ta cần bản sao có ngày ở hai ranh giới tháng.
Để thử ranh giới tháng, ví dụ sau thêm hai lần chấm vào một bản dữ liệu riêng. Một lần chấm ở ngày 31/8, lần còn lại ở ngày 1/10. Chúng chỉ tồn tại trong ví dụ có ghi setup=ranh-thang. Hãy quan sát ba vùng thời gian trong hình:
Trước 01/09Loại khỏi khoảngTừ 01/09Giữ các ngày tháng 9Từ 01/10Loại khỏi khoảng
Câu đếm dưới đây đọc bảng đã thêm hai dòng biên. Kết quả cho phép kiểm điều kiện mà không cần đọc lại từng lần chấm:
Dữ liệu đầu vào
scores9 dòng| id | day |
|---|---|
| 1 | 2026-09-01 |
| 2 | 2026-09-02 |
| 3 | 2026-09-01 |
| 4 | 2026-09-01 |
| 5 | 2026-09-02 |
| 6 | 2026-09-01 |
| 7 | 2026-09-01 |
| 8 | 2026-08-31 |
| 9 | 2026-10-01 |
Câu lệnh
SELECT COUNT(*) AS attempts
FROM scores
WHERE day >= '2026-09-01' AND day < '2026-10-01';Kết quả
| attempts |
|---|
| 7 |
attempts vẫn bằng 7 vì hai dòng mới nằm ngoài tháng. Đổi < '2026-10-01' thành <= sẽ lấy thêm dòng ngày 1/10. Đó là lỗi ở ranh giới cuối, dù truy vấn cũ vẫn đúng trên dữ liệu chỉ có tháng 9.
Chuỗi dạng DD/MM/YYYY không có cùng quy tắc sắp xếp thời gian. Ngày phải được lưu nhất quán và kiểm hợp lệ khi nhập. Một ô có chữ chưa chắc chứa ngày hợp lệ. Chẳng hạn, dù viết đúng dạng năm–tháng–ngày, chuỗi 2026-09-31 vẫn không phải ngày có thật.
Tạo nhãn tháng và tính ngày mới
Hàm strftime định dạng thời gian thành chuỗi theo mẫu. Trong mẫu '%Y-%m', %Y là năm và %m là tháng hai chữ số. Câu sau tạo nhãn tháng cho từng lần chấm mà chưa gom các lần chấm lại:
Dữ liệu đầu vào
scores7 dòng| id | day |
|---|---|
| 1 | 2026-09-01 |
| 2 | 2026-09-02 |
| 3 | 2026-09-01 |
| 4 | 2026-09-01 |
| 5 | 2026-09-02 |
| 6 | 2026-09-01 |
| 7 | 2026-09-01 |
Câu lệnh
SELECT id, strftime('%Y-%m', day) AS month
FROM scores
ORDER BY id;Kết quả
| id | month |
|---|---|
| 1 | 2026-09 |
| 2 | 2026-09 |
| 3 | 2026-09 |
| 4 | 2026-09 |
| 5 | 2026-09 |
| 6 | 2026-09 |
| 7 | 2026-09 |
Bảy dòng đều có month là 2026-09 vì dữ liệu nguồn cùng thuộc tháng đó. Nếu muốn mỗi tháng chỉ có một dòng, bạn cần nhóm theo nhãn tháng vừa tính. Tạo nhãn chỉ thêm thông tin cho từng lần chấm. Bạn vẫn phải dùng hàm tổng hợp nếu muốn đếm số lần chấm của cả tháng.
Hàm date trả ngày dạng YYYY-MM-DD và nhận thêm chỉ dẫn thay đổi ngày. Chuỗi '+1 day' yêu cầu tính ngày liền sau ngày đã cho. Truy vấn sau dùng các giá trị ghi trực tiếp để kiểm điểm chuyển tháng:
Câu lệnh
SELECT date('2026-09-30', '+1 day') AS next_day,
strftime('%Y-%m', '2026-09-02') AS month;Kết quả
| next_day | month |
|---|---|
| 2026-10-01 | 2026-09 |
next_day là 2026-10-01, còn month là 2026-09. Hàm ngày xử lý việc chuyển tháng theo lịch, thay vì cộng trực tiếp vào phần chữ cuối. Truy vấn chỉ hiển thị hai giá trị vừa tính. Ngày chấm đã lưu trong bảng scores vẫn giữ nguyên.
Lỗi thường gặp
| Lỗi | Vì sao sai | Cách sửa |
|---|---|---|
Nghĩ TRIM xóa cả khoảng trắng giữa tên | Hàm mặc định chỉ làm gọn hai đầu | Dùng cách xử lý riêng nếu cần bỏ dấu cách thừa ở giữa tên |
| Dùng vị trí 2 để lấy ký tự đầu | SUBSTR ở ví dụ này bắt đầu từ 1 | Ghi vị trí 1 và số ký tự cần lấy |
Nối trực tiếp mentor có thể NULL | Một phần NULL làm cả phép nối thành NULL | Dùng COALESCE cho phần có thể thiếu |
| Lấy cả ngày đầu tháng sau | Điều kiện cuối dùng <= | Dùng khoảng >= đầu tháng và < đầu tháng sau |
Tóm tắt
- Hàm chuỗi tạo giá trị mới để hiển thị trong kết quả. Truy vấn
SELECTvẫn giữ nguyên dữ liệu đã lưu. TRIMlàm gọn hai đầu,LENGTHđếm ký tự vàSUBSTRlấy đoạn theo vị trí.- Khi nối bằng
||, ghi rõ dấu cách và xử lý thành phầnNULL. - Ngày chuẩn cùng dạng
YYYY-MM-DDcó thể lọc bằng khoảng nửa mở. - Dùng
strftimeđể tạo nhãn thời gian theo mẫu. Dùngdateđể tính ngày mới, chẳng hạn ngày liền sau ngày đã cho.