Soal 1Apa perbaikan terbaik untuk kueri yang menulis agregasi GROUP BY yang sama dua kali — sekali di kueri utama dan sekali di subquery HAVING?
Menghilangkan Pekerjaan Berulang dan Mengoptimalkan Subquery — Contoh Kerja
Praktik menulis ulang kueri supaya agregasi yang sama tidak dihitung dua kali, subquery berkorelasi runtuh menjadi satu GROUP BY, dan tabel turunan dimateralisasi sekali via CTE — membandingkan query plan sebelum dan sesudah.
Data yang kita pakai di artikel ini — perf_sales dan employee
Di artikel sebelumnya kamu melihat penulisan ulang IN / EXISTS dan subquery berkorelasi.
Artikel ini melanjutkannya dengan penulisan ulang yang menghindari menghitung agregasi yang sama dua kali (melipat agregasi berulang menjadi satu hasil antara) dan memateralisasi tabel turunan (membangun subquery di klausa FROM sekali dan menahannya sementara) — dipraktikkan lewat contoh kerja konkret.
Jangan tulis agregasi yang sama dua kali — lipat duplikasi menjadi satu hasil antara
Saat kamu ingin menanyakan "apakah total penjualan masing-masing region di atas total rata-rata di semua region?", menulis total dan rata-rata sebagai dua subquery terpisah men-scan perf_sales dua kali.
Kalau kamu melipat agregasi yang sama ke dalam satu CTE dan menghitungnya sekali, lalu menggunakan ulang hasilnya, kamu hanya men-scan sekali.
Pola penulisan ulang adalah: temukan kasus di mana "agregasi GROUP BY yang sama muncul baik di kueri utama maupun di subquery", angkat agregasi itu menjadi satu CTE, dan minta kueri utama mereferensikan CTE alih-alih.
Hasilnya identik, dan scan duplikat hilang dari plan.
-- Sebelum: total dan "rata-rata di seluruh region" diagregasi secara terpisah (perf_sales di-scan dua kali)
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
)
);
-- Sesudah: agregasi berdasarkan region sekali di 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;
Runtuhkan subquery berkorelasi menjadi agregasi sekali jalan
Saat kondisi seperti "apakah total penjualan masing-masing region di atas total per-karyawan maksimum di dalam region itu?" dihitung sebagai subquery berkorelasi per baris region luar, kueri dalam dievaluasi ulang sekali untuk setiap region.
Ini juga bisa dilipat menjadi agregasi satu kali yang berkunci region, lalu di-join kembali.
Pola penulisan ulang adalah: ambil (SELECT agregat ... WHERE child.key = parent.key) yang dipanggil per baris luar dan ubah menjadi "bangun tabel agregat GROUP BY key sekali, lalu join kembali ke parent".
Evaluasi ulang per region digantikan dengan satu agregasi plus join.
-- Sebelum: korelasi per region untuk menemukan "total maksimum per-karyawan"
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;
-- Sesudah: agregasi sekali berdasarkan (region, emp_id), lalu ambil MAX per region
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;
Memateralisasi tabel turunan — bangun subquery klausa FROM sekali
Tabel turunan — subquery yang ditulis di klausa FROM — yang dirujuk di dua tempat kadang-kadang bisa dihitung ulang dua kali oleh optimizer.
Kalau kamu membangunnya sekali dan menahannya sementara (memateralisasinya), referensi tidak membangun ulang setiap kali.
Di SQLite, mereferensikan CTE beberapa kali membuat EXPLAIN QUERY PLAN menampilkan baris MATERIALIZE — mengonfirmasi bahwa CTE dimateralisasi tepat sekali.
Di latihan terakhir artikel ini kamu akan mengambil tabel turunan berat yang mengagregasi perf_sales berdasarkan emp_id dan dirujuk dua kali, mengubahnya menjadi CTE yang dimateralisasi sekali, dan mengonfirmasi bahwa MATERIALIZE muncul di plan dan scan terjadi hanya sekali.
-- Sebelum: tabel turunan yang sama dirujuk di dua klausa 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;
-- Sesudah: tabel turunan sebagai CTE yang dirujuk dua kali (dimateralisasi sekali)
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;
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Penulisan ulang mana yang meruntuhkan korelasi yang memanggil (SELECT agregat ... WHERE child.key = parent.key) untuk setiap baris grup luar?
Soal 3Apa yang ditunjukkan MATERIALIZE di EXPLAIN QUERY PLAN?