Trong bài này
Câu lạc bộ cần lưu số điện thoại để gửi vé tham dự. Một bạn đề nghị dùng số nguyên vì điện thoại chỉ gồm chữ số. Khi nhập thử, số không ở đầu biến mất. Cùng lúc đó, giá vé lại cần cộng chính xác, còn ngày tham dự cần lọc đúng tháng.
Chữ số có thể là mã, cũng có thể là số lượng
Kiểu dữ liệu (data type) mô tả loại giá trị và những phép xử lý phù hợp. Số vé bán được là số lượng, nên có thể cộng. Số điện thoại là mã liên hệ, nên việc cộng hai số điện thoại không mang ý nghĩa. Chọn kiểu theo mục đích sử dụng giúp giữ thông tin ngay từ lúc nhập.
Trong SQLite, INTEGER dùng cho số nguyên và TEXT dùng cho chữ. Các chữ số nằm trong dấu nháy đơn là một chuỗi, chẳng hạn '0901234567'. Cùng dãy chữ số đó, nếu viết như số nguyên, bạn không còn giữ được cách viết có số không ở đầu. Tên cột hay nhãn giao diện không thể khôi phục phần đã mất.
So sánh trực tiếp hai cách viết cùng thông tin liên hệ:
Câu lệnh
SELECT '0901234567' AS phone_text,
901234567 AS phone_number,
typeof('0901234567') AS text_type,
typeof(901234567) AS number_type;Kết quả
| phone_text | phone_number | text_type | number_type |
|---|---|---|---|
| 0901234567 | 901234567 | text | integer |
Ô phone_text giữ nguyên dãy bắt đầu bằng 0, còn phone_number không có chữ số đó. Hàm typeof() trả tên loại giá trị trong SQLite. Ở đây kết quả là 'text' và 'integer'. Dùng TEXT cũng cho phép giữ dấu cộng hoặc dấu cách nếu quy tắc nhập liệu cho phép.
Bạn có thể phân loại những dữ liệu cần lưu trước khi viết bảng:
| Thông tin | Kiểu dự kiến | Điều cần thống nhất |
|---|---|---|
| Điện thoại | TEXT | Cách viết và kiểm định dạng |
| Số vé | INTEGER | Có cho bằng 0 hay không |
| Giá vé theo đồng | INTEGER | Đơn vị đồng, miền không âm |
| Ngày tham dự | TEXT trong bài SQLite | Dạng YYYY-MM-DD |
Kiểu chữ chưa chứng minh số điện thoại hợp lệ hoặc thuộc về người nhập. Kiểu số nguyên cũng chưa chứng minh số vé không âm. Bạn cần tách câu hỏi “lưu loại gì” khỏi câu hỏi “giá trị nào được phép”. Mục sau sẽ cho thấy vì sao ngay tên kiểu cũng cần đọc theo đúng hệ quản trị.
SQLite thường có thể chuyển kiểu khi lưu
SQLite thông thường gắn loại lưu trữ với từng giá trị. Ưu tiên kiểu (type affinity) là quy tắc cột dùng để thử chuyển giá trị sang loại phù hợp. Khai báo cột INTEGER không có nghĩa mọi đầu vào sai kiểu đều bị từ chối. Nếu không chuyển được, một bảng thường vẫn có thể giữ văn bản ở cột đó.
Để thấy rõ, mục này tạo bảng thử riêng tên type_samples. Bảng không thuộc năm bảng chung và chỉ dùng trong ví dụ. Hai dòng được nhập lần lượt bằng chuỗi số và chuỗi không phải số:
Câu lệnh
INSERT INTO type_samples(id, amount)
VALUES (1, '20'), (2, 'abc');
SELECT id, amount, typeof(amount) AS stored_type
FROM type_samples
ORDER BY id;Bảng type_samples sau khi chạy
type_samples2 dòng thay đổi| id | amount |
|---|---|
| 1 | 20 |
| 2 | abc |
| id | amount | stored_type |
|---|---|---|
| 1 | 20 | integer |
| 2 | abc | text |
Dòng 1 có amount = 20 và loại 'integer'. SQLite chuyển '20' thành số nguyên vì chuyển được chính xác. Dòng 2 có amount = 'abc' và loại 'text'. Câu lệnh chạy thành công dù tên kiểu cột là INTEGER.
Hãy dùng kết quả này để sửa một nhận định sai: ghi tên kiểu chưa đủ bảo đảm dữ liệu sạch. Tên amount cũng không khiến SQLite hiểu đây là tiền. Nếu một chương trình cần toàn bộ giá trị là số nguyên, nó phải chọn cách kiểm chặt hơn. Đừng chờ đến báo cáo cuối tháng mới phát hiện văn bản lẫn trong số lượng.
STRICT kiểm chặt kiểu sau chuyển đổi hợp lệ
Bảng kiểm kiểu chặt (strict table) dùng từ khóa STRICT sau phần khai báo cột. SQLite chỉ nhận các tên kiểu được hỗ trợ trong chế độ này. Với một cột INTEGER, giá trị phải chuyển thành số nguyên mà không làm mất thông tin. Chế độ này không có nghĩa mọi đầu vào dưới dạng chuỗi đều bị cấm.
Lệnh CREATE TABLE tạo bảng mới, còn INSERT INTO thêm dòng. Các ví dụ chỉ dùng hai lệnh này để quan sát kiểu. Bài 24 sẽ luyện cách nhập dữ liệu đầy đủ hơn. Mỗi lượt minh họa ở đây dùng một bản dữ liệu mới, nên không làm thay đổi bảng của lượt khác.
Tạo bảng thử chặt, sau đó nhập một chuỗi biểu diễn số nguyên:
Câu lệnh
INSERT INTO strict_samples(id, amount)
VALUES (1, '20');
SELECT id, amount, typeof(amount) AS stored_type
FROM strict_samples;Bảng strict_samples sau khi chạy
strict_samples1 dòng thay đổi| id | amount |
|---|---|
| 1 | 20 |
| id | amount | stored_type |
|---|---|---|
| 1 | 20 | integer |
Giá trị được lưu là số nguyên 20, không còn là chuỗi. Đây là chuyển đổi hợp lệ, nên STRICT chấp nhận. Ngược lại, số có phần lẻ không thể biến thành số nguyên mà giữ nguyên giá trị. SQLite từ chối thay vì âm thầm làm tròn.
Thử trường hợp phải báo lỗi để thấy giới hạn đó:
Câu lệnh
INSERT INTO strict_samples(id, amount)
VALUES (1, 20.5);Thông báo lỗi
cannot store REAL value in INTEGER column strict_samples.amount
strict_samples0 dòng| id | amount |
|---|---|
| Không có dòng nào | |
Thông báo lỗi nói không thể lưu giá trị số thực vào cột số nguyên. Mỗi ví dụ chạy trên bảng mới, nên lỗi này chỉ có thể do sai kiểu. Nếu thay 20.5 bằng 'abc', giá trị cũng không chuyển được và bị từ chối. Khi đọc lỗi, hãy xác định đang sai kiểu hay đang vi phạm một quy tắc khác.
Kiểu đúng vẫn cần quy tắc về giá trị
Giá vé có thể là số nguyên nhưng vẫn âm hoặc bị bỏ thiếu. Ràng buộc (constraint) là quy tắc mà cơ sở dữ liệu kiểm khi ghi. NOT NULL bắt buộc có giá trị. CHECK kiểm điều kiện, chẳng hạn price >= 0 để cấm giá âm.
Ví dụ sau tạo bảng vé rỗng với điện thoại bắt buộc và giá tính theo đồng. Bảng tickets là dữ liệu minh họa mới, không phải dữ liệu website. Dùng số nguyên cho giá giúp tính chính xác trong đơn vị đã chọn:
Câu lệnh
CREATE TABLE tickets (
id INTEGER PRIMARY KEY,
phone TEXT NOT NULL,
price INTEGER NOT NULL CHECK(price >= 0)
) STRICT;
SELECT name, type, "notnull" AS required
FROM pragma_table_info('tickets')
ORDER BY cid;Kết quả
| name | type | required |
|---|---|---|
| id | INTEGER | 0 |
| phone | TEXT | 1 |
| price | INTEGER | 1 |
Kết quả mô tả ba cột vừa tạo. Hàm bảng pragma_table_info() đọc thông tin cấu trúc, không đọc những chiếc vé. Cột required có giá trị 1 khi khai báo NOT NULL rõ ràng. CHECK không xuất hiện trong bảng thông tin cột này, nên muốn kiểm hành vi còn phải thử giá trị biên.
Phân biệt các quy tắc theo câu hỏi chúng trả lời:
LoạiCó chuyển được thành số nguyên?Bắt buộcCó thiếu giá trị không?MiềnSố có không âm không?Ý nghĩaĐơn vị đã thống nhất chưa?
CHECK(price >= 0) riêng lẻ không cấm NULL. Trong SQLite, điều kiện kiểm trả NULL không bị coi là sai. Vì vậy cột bắt buộc cần cả NOT NULL. Tương tự, TEXT NOT NULL vẫn chấp nhận chuỗi rỗng '', nếu bạn chưa thêm quy tắc kiểm nội dung.
Giá tiền cần chính xác cũng không nên mặc định dùng REAL, tức kiểu số thực gần đúng. Bạn có thể lưu số nguyên theo đơn vị nhỏ nhất đã chọn. Khi dùng PostgreSQL, kiểu số thập phân chính xác NUMERIC là một lựa chọn khác. Cả hai cách đều cần ghi rõ đơn vị và phạm vi giá trị.
Ngày dạng chữ cần cùng một quy ước
Bảng scores lưu day dưới dạng TEXT với mẫu YYYY-MM-DD: năm, tháng, ngày có độ rộng cố định. Khi mọi giá trị cùng định dạng, thứ tự chữ phù hợp thứ tự ngày. Điều kiện lấy từ đầu tháng tới trước đầu tháng sau giúp không phải tự tính ngày cuối tháng.
Lấy các lần chấm trong tháng 9 từ bộ dữ liệu chung:
Dữ liệu đầu vào
scores7 dòng| id | score | day |
|---|---|---|
| 1 | 80 | 2026-09-01 |
| 2 | 90 | 2026-09-02 |
| 3 | 70 | 2026-09-01 |
| 4 | 90 | 2026-09-01 |
| 5 | 100 | 2026-09-02 |
| 6 | 50 | 2026-09-01 |
| 7 | 90 | 2026-09-01 |
Câu lệnh
SELECT id, score, day
FROM scores
WHERE day >= '2026-09-01'
AND day < '2026-10-01'
ORDER BY id;Kết quả
| id | score | day |
|---|---|---|
| 1 | 80 | 2026-09-01 |
| 2 | 90 | 2026-09-02 |
| 3 | 70 | 2026-09-01 |
| 4 | 90 | 2026-09-01 |
| 5 | 100 | 2026-09-02 |
| 6 | 50 | 2026-09-01 |
| 7 | 90 | 2026-09-01 |
Các lần chấm mẫu đều nằm trong khoảng nên đều xuất hiện. Dấu < loại chính mốc bắt đầu tháng tiếp theo. Cách này dựa trên quy ước lưu nhất quán, không dựa vào tên cột day. Nếu trộn '01/09/2026' vào cùng cột, phép so sánh chữ không còn đảm bảo thứ tự lịch mong muốn.
Bạn nên thống nhất cách biểu diễn trước khi nhập, rồi kiểm giá trị ở nơi nhận dữ liệu. Có kiểu phù hợp giúp giảm nhầm lẫn, nhưng vẫn cần biết dữ liệu có ý nghĩa gì. Một chuỗi đúng hình dạng ngày cũng có thể chứa ngày không tồn tại. Chọn kiểu và kiểm nội dung là hai việc cùng cần làm.
Đối chiếu kiểu dữ liệu với PostgreSQL
Khi chuyển bảng vé sang PostgreSQL, bạn cần chọn lại kiểu theo dữ liệu muốn lưu. PostgreSQL có kiểu riêng cho ngày, thời điểm và giá trị đúng/sai. Bảng sau giúp đối chiếu các lựa chọn thông dụng:
| Dữ liệu hoặc hành vi | SQLite | PostgreSQL |
|---|---|---|
| Số nguyên | INTEGER | integer, bigint tùy phạm vi |
| Chữ | TEXT | text, varchar(n) giới hạn độ dài |
| Tiền chính xác | INTEGER theo đơn vị đồng | numeric(12, 2) cho số thập phân chính xác |
| Ngày hoặc thời điểm | TEXT dạng YYYY-MM-DD cho ngày | date cho ngày, timestamp cho thời điểm |
| Đúng/sai | INTEGER với quy ước 0/1 | boolean |
Nhập 'abc' vào cột số nguyên | Bảng thường có thể nhận; bảng STRICT từ chối | Luôn từ chối với cột integer, ngay ở bảng mặc định |
numeric(12, 2) dành tối đa 12 chữ số, trong đó có 2 chữ số sau dấu thập phân. Bạn vẫn cần thống nhất đơn vị tiền trước khi tính. PostgreSQL kiểm kiểu cột theo mặc định, nên không cần thêm STRICT để chặn 'abc' ở cột integer. Điểm giống nhau này không có nghĩa mọi quy tắc chuyển kiểu đều giống SQLite.
Lỗi thường gặp
| Lỗi | Vì sao sai | Cách sửa |
|---|---|---|
| Lưu điện thoại bằng số nguyên | Mất cách viết có số không đầu | Dùng TEXT và kiểm định dạng riêng |
Tin rằng INTEGER ở bảng thường cấm chữ | SQLite có quy tắc ưu tiên và chuyển kiểu | Dùng STRICT khi cần kiểm kiểu chặt |
Dùng riêng CHECK để cấm thiếu giá | Điều kiện trả NULL vẫn được chấp nhận | Thêm NOT NULL |
| Trộn các cách viết ngày | So sánh chữ không còn đúng thứ tự lịch | Thống nhất YYYY-MM-DD |
Tóm tắt
- Chọn kiểu theo ý nghĩa và phép xử lý, không chỉ theo hình dạng chữ số.
typeof()cho biết kiểu thực sự của giá trị trong SQLite.STRICTchấp nhận chuyển đổi không mất thông tin, nhưng từ chối giá trị không phù hợp.- Kiểu dữ liệu không thay tính bắt buộc, giới hạn giá trị hay quy ước đơn vị.
- Ngày dạng chữ chỉ so sánh đúng khi mọi dòng cùng dùng định dạng phù hợp.