Cột sinh tự động là cột được tính toán
Cột sinh tự động (generated column) là cột có giá trị đến từ một biểu thức, không phải từ lệnh INSERT. Bạn khai báo công thức một lần trong CREATE TABLE, phần còn lại SQLite lo. Bạn không bao giờ ghi vào cột này; thử làm vậy sẽ gây lỗi.
Ví dụ ngắn nhất có thể:
total chưa bao giờ được thêm vào, nhưng nó xuất hiện ở mọi hàng. SQLite tính lại nó từ price + tax mỗi khi bạn đọc hàng. Cập nhật một trong hai cột và total sẽ thay đổi theo.
Cụm từ khóa GENERATED ALWAYS AS là bắt buộc. ALWAYS chỉ là hình thức theo chuẩn SQL: SQLite không có lựa chọn nào khác.
VIRTUAL và STORED
Mỗi cột sinh tự động thuộc một trong hai loại. Mặc định là VIRTUAL:
Cách hình dung:
VIRTUAL: không tốn byte nào trên đĩa, tốn CPU mỗi lần đọc. Thêm dễ, sau này đổi cũng dễ.STORED: tốn dung lượng đĩa, đọc không tốn thêm gì. Đáng dùng khi biểu thức tốn kém hoặc cột được đọc thường xuyên hơn ghi rất nhiều.
Nếu không ghi từ khóa nào, bạn sẽ nhận VIRTUAL. Đó gần như luôn là lựa chọn mặc định đúng.
Dùng để làm gì? Giá trị suy ra có thể đánh index
Tính năng đắt giá nhất là bạn có thể đặt index trên cột sinh tự động. Nhờ đó bạn tra cứu nhanh trên các giá trị suy ra mà không phải viết lại từng truy vấn.
Giả sử bạn muốn tra cứu email không phân biệt hoa thường:
Index bao phủ dạng chữ thường. Truy vấn lọc theo email_lower dùng index trực tiếp. SQLite cũng có index trên biểu thức (CREATE INDEX ... ON users(lower(email))), nhưng cột sinh tự động khiến giá trị suy ra hiện ra như một cột thật mà bạn có thể SELECT, tham chiếu trong view và dùng lại từ code ứng dụng.
Lấy giá trị ra từ JSON
Cột sinh tự động phát huy tác dụng rõ nhất khi đi cùng JSON. Hỗ trợ JSON của SQLite cho bạn ->> để trích một giá trị đơn; bọc nó trong một cột sinh tự động là bạn có một trường có kiểu, có thể đánh index, nằm trên một khối dữ liệu linh hoạt.
Với truy vấn của bạn, user_id và kind trông như các cột bình thường, nhưng dữ liệu nằm trong payload. Thay đổi JSON, các cột sẽ cập nhật theo. Index trên user_id giúp việc tra cứu nhanh.
Quy tắc và ràng buộc
Vài điều SQLite bắt buộc, nên biết trước khi đụng phải:
- Biểu thức phải tất định (deterministic). Không được dùng
random(),datetime('now')hay các hàm không tất định khác. Giá trị phải tái tạo được từ chính hàng đó. - Biểu thức chỉ được tham chiếu các cột trong cùng hàng. Không truy vấn con, không hàm tổng hợp, không bảng khác.
- Bạn không thể
INSERThayUPDATEtrực tiếp một cột sinh tự động.INSERT INTO products (total) VALUES (5)sẽ báo lỗi. - Không thể thêm cột
STOREDbằngALTER TABLE ... ADD COLUMN. Chỉ cộtVIRTUALmới thêm được sau khi tạo bảng. - Cột sinh tự động có thể có ràng buộc
NOT NULL,CHECK,UNIQUEvà cảFOREIGN KEY. Về những mặt đó, chúng hoạt động như mọi cột khác.
Minh họa nhanh quy tắc về ghi:
sqlite> INSERT INTO products (price, tax, total) VALUES (10, 1, 999);
Runtime error: cannot INSERT into generated column "total"
Cách sửa là bỏ cột sinh tự động khỏi danh sách cột của INSERT và để SQLite tự tính.
Chọn VIRTUAL hay STORED
Quyết định thường phụ thuộc vào tỷ lệ đọc/ghi và chi phí của biểu thức:
Kinh nghiệm thực tế:
- Mặc định dùng
VIRTUAL. Nó không tốn gì lúc ghi và ổn với gần như mọi trường hợp. - Chuyển sang
STOREDkhi bạn đánh index cho cột trên một bảng ghi nhiều (dù sao index cũng cần giá trị được lưu lại), hoặc khi biểu thức thực sự tốn kém. - Đừng quá đắn đo. Loại cột là một phần của cấu trúc bảng, nhưng bạn có thể xóa và tạo lại cột nếu đổi ý, ít nhất là với
VIRTUAL.
Cột sinh tự động và view
Cột sinh tự động có phần chồng lấn với view: cả hai đều đưa ra giá trị tính toán mà không lưu chúng (ừm, đôi khi thôi). Cách phân chia thường là:
- Cột sinh tự động thuộc về một hàng và một bảng. Dùng nó cho các phép suy ra theo từng hàng: định dạng email, trích một trường JSON, tính tổng.
- View là một truy vấn được lưu lại. Dùng nó khi phép tính cần join, tổng hợp hoặc lọc trên nhiều hàng.
Bạn có thể kết hợp cả hai. Một view có thể SELECT từ bảng có cột sinh tự động và join thêm thông tin khác. Cột sinh tự động nằm ở tầng lưu trữ; view nằm ở tầng truy vấn.
Tiếp theo: ATTACH DATABASE
Cột sinh tự động giúp một bảng tự tính giá trị của chính nó. Trang tiếp theo đi theo hướng ngược lại: kết nối nhiều cơ sở dữ liệu SQLite cùng lúc bằng ATTACH DATABASE, để một truy vấn có thể trải rộng trên nhiều tệp.
Câu hỏi thường gặp
Cột sinh tự động trong SQLite là gì?
Cột sinh tự động (generated column) là cột có giá trị được tính từ một biểu thức dựa trên các cột khác trong cùng hàng. Bạn khai báo nó bằng GENERATED ALWAYS AS (expression) trong CREATE TABLE. Bạn không bao giờ ghi trực tiếp vào cột này: SQLite tự tính giá trị mỗi khi hàng được đọc hoặc lưu.
Cột sinh tự động VIRTUAL và STORED khác nhau thế nào?
Cột VIRTUAL được tính lại mỗi lần đọc và không chiếm chỗ trên đĩa; đây là giá trị mặc định. Cột STORED được tính một lần lúc ghi và lưu vào tệp cơ sở dữ liệu, giúp đọc rẻ hơn nhưng ghi tốn hơn một chút. Cả hai đều đánh index được, nhưng STORED thường là lựa chọn đúng khi biểu thức nặng hoặc cột được đọc nhiều hơn ghi rất nhiều.
Có thể đánh index cho cột sinh tự động trong SQLite không?
Có. CREATE INDEX dùng được trên cột sinh tự động, cả VIRTUAL lẫn STORED. Đây là lý do chính để dùng chúng: bạn có thể đánh index cho một giá trị suy ra (như lower(email) hay một trường JSON trích bằng ->>) và để bộ lập kế hoạch truy vấn dùng index đó mà không phải viết lại từng truy vấn.
Có thể dùng ALTER TABLE để thêm cột sinh tự động không?
Có, nhưng chỉ với cột VIRTUAL. ALTER TABLE ... ADD COLUMN ... GENERATED ALWAYS AS (...) VIRTUAL chạy bình thường. Thêm cột sinh tự động STORED bằng ALTER TABLE thì không được hỗ trợ: bạn sẽ phải tạo lại bảng. Hãy tính trước nếu bạn muốn có cột stored trên bảng đã tồn tại.