CTE là một truy vấn con có tên
Common Table Expression (CTE) là một truy vấn con (subquery) được tách ra và đặt tên. Thay vì lồng một câu SELECT bên trong một câu SELECT khác, bạn định nghĩa nó ở đầu bằng WITH, đặt tên, rồi dùng tên đó trong truy vấn chính như một bảng.
Cấu trúc lúc nào cũng như nhau:
Đọc từ trên xuống: đầu tiên tạo một kết quả có tên customer_totals, sau đó truy vấn kết quả đó. CTE hoạt động như một view tạm thời, chỉ tồn tại trong đúng câu lệnh này.
Cùng truy vấn đó nhưng không dùng CTE
Đây là cùng logic đó viết bằng truy vấn con, để bạn thấy CTE đang thay thế cái gì:
Kết quả như nhau. Nhưng để ý thứ tự đọc: mắt bạn phải chui vào trong cặp ngoặc, hiểu phần đang được tính, rồi mới quay ra ngoài. Bản dùng CTE đọc theo đúng thứ tự công việc diễn ra: định nghĩa kết quả trung gian, rồi dùng nó. Với truy vấn nhỏ thì hai cách ngang nhau. Với truy vấn ba bốn bước, đó là khác biệt giữa code đọc lướt được và code phải giải mã.
Nhiều CTE trong một truy vấn
Bạn có thể nối nhiều CTE với nhau, ngăn cách bằng dấu phẩy. Mỗi CTE có thể tham chiếu những CTE định nghĩa trước nó, nên bạn xây được một chuỗi các bước có tên:
Một WITH, sau đó là các định nghĩa CTE ngăn cách bằng dấu phẩy. CTE thứ hai (big_spenders) đọc từ CTE thứ nhất (customer_totals) giống như đọc từ một bảng. Câu SELECT chính đứng sau định nghĩa CTE cuối cùng.
Một lỗi hay gặp: viết lại WITH trước CTE thứ hai. Đừng làm vậy, đó là lỗi cú pháp. Một WITH dùng cho tất cả.
Tham chiếu một CTE nhiều lần
Đây là chỗ CTE thật sự vượt trội so với truy vấn con. Nếu bạn cần cùng một kết quả trung gian ở hai chỗ, CTE cho phép tính một lần và tham chiếu hai lần:
CTE được tham chiếu hai lần: một lần để tính trung bình, một lần làm nguồn dữ liệu chính. Không có CTE, bạn sẽ phải lặp lại truy vấn GROUP BY, và mỗi lần sửa phải sửa ở hai chỗ.
CTE với INSERT, UPDATE và DELETE
CTE không chỉ dành cho SELECT. Bạn có thể đặt mệnh đề WITH trước INSERT, UPDATE hoặc DELETE để dùng một truy vấn con có tên trong thao tác ghi:
CTE mô tả những hàng cần gắn cờ. Câu INSERT ... SELECT dùng nó làm nguồn. Mẹo tương tự cũng áp dụng cho DELETE FROM ... WHERE id IN (SELECT id FROM cte) khi cần xóa theo từng bước mà logic chọn hàng khá rắc rối.
Khi nào nên dùng CTE
Vài quy tắc kinh nghiệm:
- Truy vấn có nhiều hơn một bước logic. Tổng hợp, rồi lọc theo kết quả tổng hợp, rồi join kết quả đó: đó là một chuỗi xử lý, và mỗi bước một CTE giúp nó dễ đọc.
- Nếu không, bạn sẽ phải lặp lại cùng một truy vấn con. Định nghĩa một lần, tham chiếu hai lần.
- Truy vấn con xứng đáng có một cái tên. Nếu bạn định viết chú thích phía trên truy vấn con để giải thích nó đại diện cho gì, thì tên CTE chính là chú thích đó, và cú pháp buộc bạn phải có nó.
- Bạn sắp viết một truy vấn đệ quy. Điều đó chỉ làm được với
WITH RECURSIVE, được trình bày ở trang tiếp theo.
Khi không cần dùng:
- Một truy vấn con đơn giản dùng ở đúng một chỗ.
WHERE id IN (SELECT id FROM ...)để nguyên là ổn. - Truy vấn cần hiệu năng cao mà bạn đã kiểm chứng rằng viết gộp logic vào giúp ích. SQLite thường ít coi CTE là rào chắn tối ưu hóa hơn một số cơ sở dữ liệu khác, nhưng với những đường chạy nóng thì nên kiểm tra bằng
EXPLAIN QUERY PLAN.
Một ví dụ hoàn chỉnh
Ghép tất cả lại: một báo cáo nhỏ tìm đơn hàng lớn nhất của mỗi khách hàng và so sánh nó với mức trung bình của họ:
Hai CTE, mỗi CTE làm một việc. Câu SELECT chính định dạng kết quả. Bạn có thể đọc truy vấn từ trên xuống và hiểu từng bước riêng lẻ, và đó chính là ý nghĩa của CTE.
Tiếp theo: CTE đệ quy
Mọi thứ từ đầu tới giờ đều là CTE thông thường: một truy vấn con có tên, được tính một lần. SQLite còn hỗ trợ WITH RECURSIVE, khi đó một CTE tham chiếu chính nó để duyệt cấu trúc phân cấp, sinh dãy số hoặc duyệt đồ thị. Đó là nội dung của trang tiếp theo.
Câu hỏi thường gặp
CTE trong SQLite là gì?
Common Table Expression là một truy vấn con được đặt tên, nằm ở đầu câu lệnh SELECT, INSERT, UPDATE hoặc DELETE. Bạn khai báo nó bằng từ khóa WITH, đặt cho nó một cái tên, rồi dùng tên đó trong truy vấn chính như thể đó là một bảng. CTE giúp truy vấn phức tạp dễ đọc hơn vì bạn xây kết quả từng bước một.
CTE khác truy vấn con trong SQLite ở điểm nào?
Hai cách có thể cho kết quả giống hệt nhau: về bản chất, CTE là một truy vấn con được tách ra và đặt tên. Khác biệt nằm ở độ dễ đọc và khả năng dùng lại: một CTE có thể được tham chiếu nhiều lần trong cùng truy vấn, và tên của nó cho biết kết quả trung gian đó đại diện cho điều gì. Với bộ lọc dùng một lần, truy vấn con là đủ; với logic nhiều bước, CTE tốt hơn.
Có thể dùng nhiều CTE trong một truy vấn SQLite không?
Có. Sau WITH đầu tiên, ngăn cách các CTE tiếp theo bằng dấu phẩy, đừng lặp lại WITH. Mỗi CTE có thể tham chiếu các CTE được định nghĩa trước nó, nhờ vậy bạn xây được một chuỗi các bước có tên. Câu SELECT chính đứng sau CTE cuối cùng.