Agregasi: Count, Sum, Avg, dan Grouping
Hitung statistik langsung di database, bukan di PHP.
Kenapa Agregasi Harus di Database?
Menghitung total atau rata-rata adalah pekerjaan database, bukan PHP. Database dirancang untuk itu: ia memindai kolom lewat index dan mengembalikan satu angka. Kalau kamu memuat semua baris ke PHP dulu baru dihitung, memori habis dan waktunya bisa ratusan kali lebih lama. Aturan emas performa: agregasi di database, bukan di PHP.
Fungsi Agregasi Dasar
// app/Http/Controllers/DashboardController.php
$total = Post::count();
$rataViews = Post::avg('views');
$tertinggi = Post::max('views');
$totalViews = Post::sum('views');
$adaSpam = Post::where('spam', true)->exists(); // booleanexists() layak disebut khusus: ia mengembalikan boolean dan berhenti segera setelah menemukan satu baris yang cocok. Jauh lebih murah daripada count() > 0 kalau kamu cuma perlu tahu "ada atau tidak".
Jebakan klasik pemula: Post::all()->count(). all() memuat SEMUA baris menjadi model Eloquent di memori, baru dihitung. Untuk 100 ribu baris itu bisa menghabiskan ratusan MB dan membuat server ngos-ngosan. Post::count() hanya mengirim SELECT COUNT(*) dan menerima satu angka. Bedanya bisa 100x lipat, dan ini bug performa paling sering ditemukan di code review.
Group By untuk Statistik per Kelompok
// app/Http/Controllers/LaporanController.php
use Illuminate\Support\Facades\DB;
$perKategori = Post::select('category_id', DB::raw('count(*) as total'))
->groupBy('category_id')
->orderByDesc('total')
->get();
foreach ($perKategori as $row) {
echo "Kategori {$row->category_id}: {$row->total} post";
}DB::raw() dipakai karena count(*) adalah ekspresi SQL yang tidak dimengerti query builder secara otomatis. Hasilnya punya atribut virtual total yang bisa diakses seperti kolom biasa.
Having: Filter Setelah Agregasi
// app/Http/Controllers/LaporanController.php
$populer = Post::select('category_id', DB::raw('count(*) as total'))
->groupBy('category_id')
->having('total', '>', 10)
->get();Bedakan dua filter ini baik-baik: where memfilter baris SEBELUM dikelompokkan, having memfilter hasil SETELAH agregasi. Mau kategori yang total postnya di atas 10? Itu having. Mau hanya menghitung post yang sudah published? Itu where, ditulis sebelum groupBy.
Catatan teknis: Di mode strict MySQL (default Laravel), setiap kolom di select() harus ada di groupBy() atau dibungkus fungsi agregasi, kalau tidak query error. Itu sebabnya contoh di atas hanya me-select category_id dan count(). Kalau butuh nama kategorinya, join tabel categories lalu group by categories.id dan categories.name sekaligus. Alternatif yang lebih bersih dari DB::raw() adalah selectRaw(): Post::selectRaw('category_id, count() as total')->groupBy('category_id').
Agregasi per Periode Waktu
Laporan "jumlah post per bulan" adalah kasus klasik group by tanggal:
// app/Http/Controllers/LaporanController.php
$perBulan = Post::selectRaw("DATE_FORMAT(created_at, '%Y-%m') as bulan, count(*) as total")
->groupBy('bulan')
->orderBy('bulan')
->get();Fungsi tanggalnya tergantung database: DATE_FORMAT untuk MySQL, to_char untuk PostgreSQL, strftime untuk SQLite. Kalau aplikasimu harus jalan di banyak database, tulis sesuai database production yang dipakai dan catat di dokumentasi.
Jebakan: groupBy('bulan') memakai alias dari select. Ini didukung MySQL dan PostgreSQL, tapi tidak semua database mengizinkan group by alias. Alternatif yang aman adalah mengulang ekspresi lengkapnya di groupBy().
Tantangan
Statistik Blog
Via tinker, hitung dalam SATU query per item: total post, rata-rata views, post dengan views tertinggi (tampilkan judulnya). Jangan pakai all().