Affinity là ưu tiên, không phải quy tắc
SQLite dùng kiểu động. Mỗi giá trị tự mang lớp lưu trữ (storage class) của nó (NULL, INTEGER, REAL, TEXT, BLOB), và kiểu khai báo của cột không giới hạn chặt chẽ những gì bạn có thể đưa vào. Điều kiểu khai báo thực sự làm là gán cho cột một affinity: lớp lưu trữ ưu tiên mà SQLite cố chuyển các giá trị đi vào sang.
Hãy xem chuyện gì xảy ra khi affinity không đủ để chặn một giá trị lệch kiểu:
Hàng thứ hai lưu chuỗi 'two' vào một cột INTEGER. SQLite đã thử chuyển 'two' thành số, không được (vì nó không phải số), và vẫn lưu nó dưới dạng TEXT. typeof() cho thấy lớp lưu trữ thật của từng giá trị, và nó không phải lúc nào cũng khớp với khai báo của cột.
Điều này khiến người quen Postgres hay MySQL bất ngờ. Nhưng đó là thiết kế có chủ đích.
Năm loại affinity
Mỗi cột trong bảng không phải STRICT có đúng một trong các loại sau:
TEXT: ưu tiên chuỗi.NUMERIC: ưu tiên số, nhưng chấp nhận văn bản nếu không chuyển đổi được.INTEGER: giốngNUMERIC, nhưng lưu các giá trị không có phần thập phân dưới dạng số nguyên.REAL: ưu tiên số thực dấu phẩy động.BLOB: không ưu tiên gì, lưu đúng thứ bạn đưa vào.
Affinity BLOB còn được gọi là "không có affinity": đó là thứ bạn nhận được khi không khai báo kiểu nào cả.
Cùng một đầu vào là chuỗi '42', nhưng ra năm kiểu lưu trữ khác nhau. Mỗi cột chuyển đổi (hoặc không) theo affinity của nó.
SQLite chọn affinity từ khai báo của bạn như thế nào
Đây là phần khiến nhiều người vấp: SQLite không có danh sách cố định các kiểu "hợp lệ". Bạn có thể viết gần như bất cứ thứ gì sau tên cột, và SQLite xác định affinity bằng cách dò tìm chuỗi con trong văn bản đó, theo thứ tự sau:
- Chứa
INT→INTEGER - Chứa
CHAR,CLOBhoặcTEXT→TEXT - Chứa
BLOB, hoặc không có kiểu →BLOB - Chứa
REAL,FLOAhoặcDOUB→REAL - Mọi trường hợp khác →
NUMERIC
Toàn bộ thuật toán chỉ có vậy. Nó giải thích được rất nhiều điều kỳ quặc:
FLOATING_POINTS thành INTEGER vì chuỗi con INT xuất hiện trong POINTS. Quy tắc khớp đầu tiên thắng, xét từ trên xuống dưới. Đó là lý do việc sao chép nguyên kiểu từ một cơ sở dữ liệu khác có thể cho ra kết quả khác với điều bạn mong đợi.
Affinity trong thực tế: chuyển đổi khi insert
Affinity quan trọng nhất ở thời điểm SQLite quyết định chuyển đổi giá trị của bạn hay lưu nguyên như cũ. Quy tắc như sau:
- Affinity
TEXT: số vàBLOBđược chuyển thành văn bản. - Affinity
NUMERIC,INTEGER,REAL: văn bản trông giống số được chuyển đổi; văn bản không giống số thì vẫn là văn bản. - Affinity
BLOB: không chuyển đổi gì cả.
Xét từng hàng:
'123'trong cộtNUMERICtrở thành số nguyên123. Việc chuyển từ văn bản sang số thành công và không mất dữ liệu.'12.5'trở thành số thực12.5.'hello'trong cộtNUMERICvẫn là văn bản, vì không có số nào để chuyển sang.- Cột
TEXTchuyển các số thành dạng chuỗi của chúng. - Cột
BLOBlưu mọi thứ đúng như được đưa vào, giữ nguyên cả kiểu.
Điểm khác biệt giữa INTEGER và REAL
Affinity INTEGER hoạt động gần như y hệt NUMERIC, chỉ khác một điểm: một giá trị như 3.0, không có phần thập phân thực sự, sẽ được lưu thành số nguyên 3 để tiết kiệm chỗ.
3.0 được lưu thành INTEGER ở cả hai cột, vì tối ưu này cũng áp dụng cho NUMERIC. 3.5 giữ phần thập phân và vẫn là REAL. Điều cần nhớ: đừng dựa vào typeof() để biết một cột được khai báo là INTEGER hay REAL. Nó cho bạn biết thứ thực sự được lưu, và điều đó có thể khác nhau ở từng hàng.
Khi affinity gây rắc rối
Sự linh hoạt này tiện lợi cho đến khi nó không còn tiện nữa. Có hai kiểu lỗi hay gặp trong code thực tế:
1. Dữ liệu xấu lọt vào. Nếu ứng dụng có bug gửi 'N/A' vào một cột INTEGER, SQLite vẫn lưu. Các truy vấn sau đó làm phép tính trên cột này sẽ trả về kết quả lạ hoặc NULL. Không lỗi, không cảnh báo, chỉ là dữ liệu hỏng một cách âm thầm.
2. Phép so sánh trở nên kỳ lạ. Việc sắp xếp và so sánh bằng xử lý các giá trị thuộc lớp lưu trữ khác nhau theo những cách khác nhau:
Số nguyên được sắp xếp theo giá trị số, sau đó các giá trị văn bản được sắp xếp theo thứ tự từ điển, và chúng đứng sau tất cả các số. Vì vậy bạn nhận được 2, 3, 10 (các số nguyên theo thứ tự số), rồi '20', '100' (các chuỗi theo thứ tự chữ cái). Không phải điều phần lớn mọi người muốn.
Nếu bạn kiểm soát các lệnh insert và kiểm tra dữ liệu cẩn thận, bảng thông thường vẫn ổn. Nếu không, hoặc nếu bạn chỉ muốn cơ sở dữ liệu tự ép kiểu giúp mình, có một lựa chọn tốt hơn.
Tiếp theo: bảng STRICT
SQLite 3.37 thêm bảng STRICT, loại bảng tắt affinity và từ chối các giá trị không khớp với kiểu đã khai báo. Bạn có kiểu động mặc định khi muốn, và ép kiểu chặt chẽ như Postgres khi không muốn. Đó là nội dung của trang tiếp theo.
Câu hỏi thường gặp
Type affinity trong SQLite là gì?
Type affinity là lớp lưu trữ ưu tiên của một cột. SQLite có năm loại: TEXT, NUMERIC, INTEGER, REAL và BLOB. Khi bạn chèn một giá trị, SQLite cố chuyển nó sang affinity của cột, nhưng nếu việc chuyển đổi làm mất dữ liệu hoặc không thể thực hiện, nó lưu giá trị nguyên như cũ. Affinity là một gợi ý, không phải ràng buộc cứng.
SQLite quyết định affinity của một cột như thế nào?
SQLite dò tìm các chuỗi con trong tên kiểu bạn viết ở CREATE TABLE, theo thứ tự: nếu có INT thì là INTEGER; nếu không, có CHAR, CLOB hoặc TEXT thì là TEXT; nếu không, có BLOB (hoặc không khai báo kiểu) thì là BLOB; nếu không, có REAL, FLOA hoặc DOUB thì là REAL; còn lại là NUMERIC. Đó là lý do VARCHAR(50) thành TEXT và BIGINT thành INTEGER: chữ bạn viết được so khớp theo mẫu.
Cột SQLite có thể chứa giá trị sai kiểu không?
Có, trong bảng thông thường. Một cột khai báo INTEGER vẫn vui vẻ lưu chuỗi 'hello', vì affinity chỉ gợi ý việc chuyển đổi. Nếu muốn ép kiểu chặt chẽ, hãy dùng bảng STRICT, loại bảng từ chối ngay các giá trị không khớp kiểu. Trang tiếp theo sẽ nói về chúng.