Stop Query Lemot! Cara Cepat Bikin JOIN Table Pakai Teknik Rahasia Ini

Stop Query Lemot! Cara Cepat Bikin JOIN Table Pakai Teknik Rahasia Ini

Source: Dev.to

Masalah Umum: JOIN Tabel Besar Tanpa Penyaringan Awal ## Prinsip Dasar: Saring Dulu, Gabung Belakangan ## Solusi: Optimasi dengan Common Table Expressions (CTE) ## Langkah 1: Saring Orders Sejak Awal ## Apa yang Berubah Secara Fundamental? ## Optimasi Lanjutan: CTE Bertingkat (Layered CTE) ## Kenapa Ini Lebih Sehat? ## Catatan Penting Khusus SQL Server ## Dampak Nyata di Produksi ## Penutup: Optimasi Itu Soal Cara Berpikir Di dunia sistem produksi, database hampir selalu menjadi bottleneck. CPU masih santai, RAM masih longgar, tapi halaman dashboard muter-mutir seperti mikir keras soal hidup. Sering kali penyebabnya bukan spesifikasi server, melainkan struktur query yang kurang cerdas. Salah satu kesalahan klasik yang sering terjadi adalah: menggabungkan (JOIN) tabel mentah yang sangat besar secara langsung, lalu berharap SQL Server “mengurus sisanya”. Spoiler: SQL Server bisa pintar, tapi bukan cenayang. Di sinilah Common Table Expressions (CTE) berperan sebagai alat berpikir sebelum bekerja. Bayangkan skenario realistis berikut: Lalu muncul query seperti ini: Sekilas terlihat normal. Masalahnya: JOIN terjadi dulu, baru WHERE menyaring (secara logis). Walaupun optimizer SQL Server bisa melakukan predicate pushdown, dalam praktik—terutama pada query kompleks—engine tetap harus: CTE memberi kita cara mengekspresikan niat ke SQL Server: “Ini data yang benar-benar saya butuhkan. Sisanya jangan disentuh.” CTE bukan sekadar gaya penulisan, tapi alat untuk mengendalikan alur kerja query. Mari kita refactor query tadi dengan pendekatan bertahap. Dalam banyak kasus nyata, ini saja sudah memangkas waktu eksekusi dari detik ke ratusan milidetik. Kita bisa melangkah lebih jauh dengan memecah logika bisnis menjadi tahap-tahap kecil. Secara mental, query ini juga lebih mudah dibaca dan dirawat. Debugger manusia senang, SQL Server pun ikut senang. Sedikit nuansa teknis yang sering disalahpahami: Keuntungannya datang dari: Jika CTE dipakai berulang atau sangat kompleks, kadang: Dalam sistem nyata (ERP, finance, academic system, e-commerce), pola ini sering menghasilkan: Waktu eksekusi turun dari: Ini murni kemenangan logika query. CTE bukan sekadar fitur SQL. Ia adalah cara memaksa diri kita untuk berpikir: “Data mana yang benar-benar relevan?” Ketika database hanya memproses data yang diperlukan, hardware bernapas lega, aplikasi terasa instan, dan tim tidak perlu panik saat traffic naik. Optimasi performa jarang soal trik rahasia. Hampir selalu soal menghormati mesin dengan struktur query yang cerdas. Di dunia produksi, itu bedanya sistem yang “jalan” dan sistem yang “tahan banting”. kamu bisa menonton versi videonya di channel YouTube berikut: 👉 @insight105 Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: SELECT c.CustomerName, o.OrderDate, SUM(oi.Quantity * oi.Price) AS TotalAmount FROM Orders o JOIN OrderItems oi ON oi.OrderId = o.Id JOIN Customers c ON c.Id = o.CustomerId WHERE o.OrderDate >= '2025-01-01' AND o.Status = 'COMPLETED' GROUP BY c.CustomerName, o.OrderDate; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: SELECT c.CustomerName, o.OrderDate, SUM(oi.Quantity * oi.Price) AS TotalAmount FROM Orders o JOIN OrderItems oi ON oi.OrderId = o.Id JOIN Customers c ON c.Id = o.CustomerId WHERE o.OrderDate >= '2025-01-01' AND o.Status = 'COMPLETED' GROUP BY c.CustomerName, o.OrderDate; CODE_BLOCK: SELECT c.CustomerName, o.OrderDate, SUM(oi.Quantity * oi.Price) AS TotalAmount FROM Orders o JOIN OrderItems oi ON oi.OrderId = o.Id JOIN Customers c ON c.Id = o.CustomerId WHERE o.OrderDate >= '2025-01-01' AND o.Status = 'COMPLETED' GROUP BY c.CustomerName, o.OrderDate; CODE_BLOCK: WITH FilteredOrders AS ( SELECT Id, CustomerId, OrderDate FROM Orders WHERE OrderDate >= '2025-01-01' AND Status = 'COMPLETED' ) SELECT c.CustomerName, fo.OrderDate, SUM(oi.Quantity * oi.Price) AS TotalAmount FROM FilteredOrders fo JOIN OrderItems oi ON oi.OrderId = fo.Id JOIN Customers c ON c.Id = fo.CustomerId GROUP BY c.CustomerName, fo.OrderDate; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: WITH FilteredOrders AS ( SELECT Id, CustomerId, OrderDate FROM Orders WHERE OrderDate >= '2025-01-01' AND Status = 'COMPLETED' ) SELECT c.CustomerName, fo.OrderDate, SUM(oi.Quantity * oi.Price) AS TotalAmount FROM FilteredOrders fo JOIN OrderItems oi ON oi.OrderId = fo.Id JOIN Customers c ON c.Id = fo.CustomerId GROUP BY c.CustomerName, fo.OrderDate; CODE_BLOCK: WITH FilteredOrders AS ( SELECT Id, CustomerId, OrderDate FROM Orders WHERE OrderDate >= '2025-01-01' AND Status = 'COMPLETED' ) SELECT c.CustomerName, fo.OrderDate, SUM(oi.Quantity * oi.Price) AS TotalAmount FROM FilteredOrders fo JOIN OrderItems oi ON oi.OrderId = fo.Id JOIN Customers c ON c.Id = fo.CustomerId GROUP BY c.CustomerName, fo.OrderDate; CODE_BLOCK: WITH FilteredOrders AS ( SELECT Id, CustomerId, OrderDate FROM Orders WHERE OrderDate >= '2025-01-01' AND Status = 'COMPLETED' ), OrderTotals AS ( SELECT fo.Id AS OrderId, fo.CustomerId, fo.OrderDate, SUM(oi.Quantity * oi.Price) AS TotalAmount FROM FilteredOrders fo JOIN OrderItems oi ON oi.OrderId = fo.Id GROUP BY fo.Id, fo.CustomerId, fo.OrderDate ) SELECT c.CustomerName, ot.OrderDate, ot.TotalAmount FROM OrderTotals ot JOIN Customers c ON c.Id = ot.CustomerId; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: WITH FilteredOrders AS ( SELECT Id, CustomerId, OrderDate FROM Orders WHERE OrderDate >= '2025-01-01' AND Status = 'COMPLETED' ), OrderTotals AS ( SELECT fo.Id AS OrderId, fo.CustomerId, fo.OrderDate, SUM(oi.Quantity * oi.Price) AS TotalAmount FROM FilteredOrders fo JOIN OrderItems oi ON oi.OrderId = fo.Id GROUP BY fo.Id, fo.CustomerId, fo.OrderDate ) SELECT c.CustomerName, ot.OrderDate, ot.TotalAmount FROM OrderTotals ot JOIN Customers c ON c.Id = ot.CustomerId; CODE_BLOCK: WITH FilteredOrders AS ( SELECT Id, CustomerId, OrderDate FROM Orders WHERE OrderDate >= '2025-01-01' AND Status = 'COMPLETED' ), OrderTotals AS ( SELECT fo.Id AS OrderId, fo.CustomerId, fo.OrderDate, SUM(oi.Quantity * oi.Price) AS TotalAmount FROM FilteredOrders fo JOIN OrderItems oi ON oi.OrderId = fo.Id GROUP BY fo.Id, fo.CustomerId, fo.OrderDate ) SELECT c.CustomerName, ot.OrderDate, ot.TotalAmount FROM OrderTotals ot JOIN Customers c ON c.Id = ot.CustomerId; - Orders → 10 juta baris - OrderItems → 50 juta baris - Customers → 1 juta baris - Membaca banyak halaman data - Mengelola memory grant besar - Melakukan hash atau merge join berat - Execution time bisa detik bahkan puluhan detik - SQL Server tidak lagi memindai seluruh tabel Orders - Jumlah baris yang masuk ke JOIN turun drastis - Memory grant lebih kecil - Execution plan lebih sederhana - Agregasi dilakukan sebelum join ke Customers - Baris hasil jauh lebih sedikit - JOIN terakhir jadi ringan dan cepat - CTE bukan temporary table - Secara default, CTE akan di-inline ke execution plan - Keuntungannya datang dari: Penyaringan dini Pengurangan baris Struktur query yang membantu optimizer - Penyaringan dini - Pengurangan baris - Struktur query yang membantu optimizer - Penyaringan dini - Pengurangan baris - Struktur query yang membantu optimizer - #temp table + index sementara bisa lebih optimal Namun sebagai default strategi, CTE adalah senjata pertama yang sangat efektif. - Penurunan logical reads hingga >70% - Waktu eksekusi turun dari: 3–5 detik → 50–200 ms - 3–5 detik → 50–200 ms - Tanpa: Upgrade server Tambah RAM Scaling horizontal - Upgrade server - Scaling horizontal - 3–5 detik → 50–200 ms - Upgrade server - Scaling horizontal