Câu 1Cách sửa tốt nhất cho truy vấn viết cùng tổng hợp GROUP BY hai lần — một lần trong truy vấn chính và một lần trong subquery HAVING — là gì?
Loại bỏ công việc lặp lại và tối ưu subquery — Ví dụ thực hành
Thực hành viết lại truy vấn để cùng một tổng hợp không bị tính hai lần, subquery tương quan sụp đổ thành một GROUP BY duy nhất, và bảng dẫn xuất được vật chất hóa một lần qua CTE — so sánh kế hoạch truy vấn trước và sau.
Dữ liệu chúng ta dùng trong bài này — perf_sales và employee
Trong bài trước bạn đã xem xét viết lại IN / EXISTS và subquery tương quan.
Bài này xây dựng trên đó với các bản viết lại tránh tính cùng một tổng hợp hai lần (gấp các tổng hợp lặp thành một kết quả trung gian duy nhất) và vật chất hóa bảng dẫn xuất (xây subquery trong mệnh đề FROM một lần và giữ tạm) — được thực hành qua các ví dụ cụ thể.
Đừng viết cùng tổng hợp hai lần — gấp phần lặp vào một kết quả trung gian
Khi bạn muốn hỏi "tổng doanh số của từng khu vực có cao hơn trung bình tổng giữa các khu vực không?", việc viết tổng và trung bình như hai subquery riêng làm perf_sales bị quét hai lần.
Nếu bạn gấp cùng tổng hợp vào một CTE và tính nó một lần, sau đó tái sử dụng kết quả, bạn chỉ quét một lần.
Mẫu viết lại là: nhận ra trường hợp "cùng tổng hợp GROUP BY xuất hiện cả trong truy vấn chính và trong subquery", nâng tổng hợp đó vào một CTE duy nhất, và để truy vấn chính tham chiếu CTE thay vào đó.
Kết quả giống hệt, và lần quét lặp biến mất khỏi kế hoạch.
-- Trước: tổng và "trung bình giữa các khu vực" được tổng hợp riêng (perf_sales quét hai lần)
SELECT region, SUM(amount) AS total
FROM perf_sales
GROUP BY region
HAVING SUM(amount) > (
SELECT AVG(t) FROM (
SELECT SUM(amount) AS t FROM perf_sales GROUP BY region
)
);
-- Sau: tổng hợp theo khu vực một lần trong CTE
WITH region_total AS (
SELECT region, SUM(amount) AS total
FROM perf_sales
GROUP BY region
)
SELECT region, total
FROM region_total
WHERE total > (SELECT AVG(total) FROM region_total)
ORDER BY region;
Sụp đổ subquery tương quan thành một tổng hợp duy nhất
Khi một điều kiện như "tổng doanh số của từng khu vực có trên tổng tối đa theo từng nhân viên trong khu vực đó không?" được tính như subquery tương quan theo từng dòng khu vực bên ngoài, truy vấn bên trong được đánh giá lại một lần cho mỗi khu vực.
Điều này cũng có thể được gấp thành một tổng hợp một lần theo khu vực, sau đó join trở lại.
Mẫu viết lại là: lấy (SELECT aggregate ... WHERE child.key = parent.key) được gọi theo từng dòng bên ngoài và biến nó thành "xây bảng tổng hợp GROUP BY key một lần, sau đó join trở lại với cha".
Việc đánh giá lại theo từng khu vực được thay bằng một tổng hợp duy nhất cộng với một join.
-- Trước: tương quan theo từng khu vực để tìm "tổng tối đa theo từng nhân viên"
SELECT region,
(SELECT MAX(s) FROM (
SELECT SUM(amount) AS s FROM perf_sales p2
WHERE p2.region = p1.region GROUP BY emp_id
)) AS max_emp_total
FROM perf_sales p1
GROUP BY region;
-- Sau: tổng hợp một lần theo (region, emp_id), sau đó lấy MAX theo khu vực
WITH emp_region AS (
SELECT region, emp_id, SUM(amount) AS s
FROM perf_sales
GROUP BY region, emp_id
)
SELECT region, MAX(s) AS max_emp_total
FROM emp_region
GROUP BY region
ORDER BY region;
Vật chất hóa bảng dẫn xuất — xây subquery FROM-clause một lần
Một bảng dẫn xuất — subquery viết trong mệnh đề FROM — được tham chiếu ở hai chỗ đôi khi có thể được bộ tối ưu tính lại hai lần.
Nếu bạn xây nó một lần và giữ tạm (vật chất hóa nó), các tham chiếu không xây lại nó mỗi lần.
Trong SQLite, tham chiếu CTE nhiều lần làm EXPLAIN QUERY PLAN hiển thị dòng MATERIALIZE — xác nhận CTE được vật chất hóa đúng một lần.
Trong bài thực hành cuối của bài này bạn sẽ lấy một bảng dẫn xuất nặng tổng hợp perf_sales theo emp_id và được tham chiếu hai lần, biến nó thành CTE được vật chất hóa một lần, và xác nhận MATERIALIZE xuất hiện trong kế hoạch và lần quét chỉ xảy ra một lần.
-- Trước: cùng bảng dẫn xuất được tham chiếu trong hai mệnh đề FROM
SELECT a.emp_id, a.tot, b.avg_tot
FROM (SELECT emp_id, SUM(amount) AS tot FROM perf_sales GROUP BY emp_id) a
CROSS JOIN (
SELECT AVG(tot) AS avg_tot FROM (
SELECT emp_id, SUM(amount) AS tot FROM perf_sales GROUP BY emp_id
)
) b
ORDER BY a.emp_id;
-- Sau: bảng dẫn xuất như CTE được tham chiếu hai lần (vật chất hóa một lần)
WITH emp_total AS (
SELECT emp_id, SUM(amount) AS tot
FROM perf_sales
GROUP BY emp_id
)
SELECT e.emp_id, e.tot, (SELECT AVG(tot) FROM emp_total) AS avg_tot
FROM emp_total e
ORDER BY e.emp_id;
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Bản viết lại nào sụp đổ tương quan gọi (SELECT aggregate ... WHERE child.key = parent.key) cho mỗi dòng nhóm bên ngoài?
Câu 3MATERIALIZE trong EXPLAIN QUERY PLAN chỉ ra điều gì?