CodeGym /Các khóa học /SQL SELF /Tạo bảng tạm thời với WITH

Tạo bảng tạm thời với WITH

SQL SELF
Mức độ , Bài học
Có sẵn

Bảng tạm thời là một cách cực hay để lưu kết quả trung gian, sau đó thao tác tiếp với chúng. Kiểu như trong lập trình ấy: thay vì copy-paste một đoạn code dài ngoằng, bạn lưu nó vào biến rồi dùng lại cho tiện. Trong thế giới SQL, mấy cái "biến" này thường chính là bảng tạm thời.

Nhưng CTE còn làm mọi thứ dễ hơn nữa — nó cho phép tạo bảng tạm thời mà không cần lằng nhằng:

  • Không cần lo xóa thủ công.

    CTE chỉ tồn tại trong lúc query chạy thôi. SQL chạy xong là CTE biến mất luôn, kiểu như trợ lý tàng hình làm xong việc rồi lặn mất tăm, không để lại dấu vết gì.

  • Code dễ đọc hơn hẳn.

    Tạo CTE dễ hơn nhiều so với việc tạo rồi xóa bảng tạm vật lý. Mọi thứ rõ ràng, nhìn phát hiểu ngay.

  • Quá hợp cho mấy task "một lần rồi thôi".

    Nếu chỉ cần xử lý dữ liệu ở một bước — CTE là best choice. Nhanh, gọn, không side effect.

Cú pháp CTE như bảng tạm thời

Làm mới lại cú pháp CTE nhé:

WITH cte_name AS (
    SELECT ...
)
SELECT ...
FROM cte_name;

Ở đây WITH tạo ra một "biểu thức bảng" tạm thời, dùng được trong toàn bộ query mà nó được định nghĩa. Nhìn thì như bảng thật, nhưng nó chỉ sống tới khi query chạy xong thôi.

Ví dụ: Tính danh sách sinh viên xuất sắc

Cùng tạo bảng tạm bằng CTE để tìm sinh viên có điểm trung bình (grade) trên 90. Sau đó in ra danh sách họ nhé.

WITH high_achievers AS (
    SELECT student_id, AVG(grade) AS avg_grade
    FROM grades
    GROUP BY student_id
    HAVING AVG(grade) > 90
)
SELECT *
FROM high_achievers;

Ở đây mình:

  • dùng WITH để tạo bảng tạm high_achievers.
  • trong CTE, gom nhóm điểm (grades) theo từng sinh viên (student_id) rồi tính điểm trung bình.
  • ở query SELECT * chỉ cần gọi bảng tạm high_achievers như bảng thường thôi.

So sánh CTE và bảng tạm thời

Đôi khi sẽ có câu hỏi: khác gì giữa dùng CTE và bảng tạm tạo bằng CREATE TEMP TABLE?

Đây là ví dụ bảng tạm truyền thống (CREATE TEMP TABLE) cho cùng dữ liệu:

CREATE TEMP TABLE high_achievers_temp AS
	SELECT student_id, AVG(grade) AS avg_grade
	FROM grades
	GROUP BY student_id
	HAVING AVG(grade) > 90;

SELECT *
FROM high_achievers_temp;

DROP TABLE high_achievers_temp;  -- Đừng quên xóa bảng nhé!

Và query tương tự dùng CTE:

WITH high_achievers AS (
    SELECT student_id, AVG(grade) AS avg_grade
    FROM grades
    GROUP BY student_id
    HAVING AVG(grade) > 90
)
SELECT *
FROM high_achievers;

Khi nào dùng CTE, khi nào dùng bảng tạm

CTE cực hợp khi bạn muốn chuẩn bị dữ liệu trung gian rồi thao tác luôn — khỏi lằng nhằng. Không cần lo xóa: CTE tự biến mất khi query xong. Nó hiện rõ trong cấu trúc SQL, không bị giấu như bảng tạm. Nhờ vậy query gọn, dễ hiểu, nhất là khi bạn xử lý nhiều bước liên tiếp. Ngoài ra, CTE còn có thể lồng nhau, kết hợp nhiều cái để xây logic phức tạp — phần này sẽ nói sau.

Cấu trúc này rất ổn khi query chỉ chạy một lần, dữ liệu chỉ cần trong phạm vi query đó. Nếu bạn muốn dùng kết quả nhiều lần ở các phần khác nhau của hệ thống, hoặc muốn giữ dữ liệu trung gian suốt phiên làm việc — bảng tạm sẽ đáng tin hơn. Đặc biệt nếu dữ liệu lớn và cần hiệu năng cao: lúc này bảng tạm vật lý chạy ổn định và nhanh hơn.

Tóm lại, tùy bài toán: CTE là công cụ nhanh, gọn cho xử lý dữ liệu cục bộ. Bảng tạm là "ngựa thồ" cho mấy kịch bản phức tạp, lâu dài.

Ví dụ: Tổng hợp dữ liệu

Giả sử có bảng enrollments lưu thông tin sinh viên đăng ký khóa học nào. Mình muốn biết mỗi khóa có bao nhiêu sinh viên, chỉ lấy khóa có hơn 5 sinh viên thôi nhé.

Dùng CTE làm như này:

WITH course_counts AS (
    SELECT course_id, COUNT(student_id) AS student_count
    FROM enrollments
    GROUP BY course_id
    HAVING COUNT(student_id) > 5
)

SELECT *
FROM course_counts
ORDER BY student_count DESC;

Nhiều CTE: Xây dựng từng bước

Nếu cần chia task thành nhiều bước thì sao? Ví dụ, đầu tiên chọn sinh viên có điểm trung bình cao, sau đó tìm các khóa học của họ? Dễ thôi!

WITH high_achievers AS (
    SELECT student_id, AVG(grade) AS avg_grade
    FROM grades
    GROUP BY student_id
    HAVING AVG(grade) > 90
),
student_courses AS (
    SELECT e.student_id, c.course_name
    FROM enrollments e
    JOIN courses c ON e.course_id = c.course_id
)

SELECT ha.student_id, ha.avg_grade, sc.course_name
FROM high_achievers ha
JOIN student_courses sc ON ha.student_id = sc.student_id;

Bây giờ query có hai CTE:

  1. high_achievers tìm sinh viên xuất sắc.
  2. student_courses ghép sinh viên với khóa học.

Kết quả là danh sách sinh viên điểm cao và các khóa học của họ.

Lỗi thường gặp khi dùng CTE

Dữ liệu quá lớn. CTE được lưu trong bộ nhớ PostgreSQL. Nếu tạo CTE với kết quả khổng lồ, query sẽ chậm hoặc thậm chí hết RAM.

Lạm dụng. Dùng CTE ở chỗ subquery thường là đủ sẽ làm mọi thứ rối rắm hơn.

Quên index. Nếu dữ liệu trong CTE lấy từ bảng lớn mà không có index, query sẽ rất chậm.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION