Topik ini membahas query builder, yaitu lapisan penyusun SQL pada Laravel yang bekerja satu tingkat di bawah Eloquent. Bahasan mencakup kondisi yang tepat untuk turun dari Eloquent, akses tabel melalui DB::table(), klausa where, whereIn, dan whereBetween, pengurutan dengan orderBy, agregat count, max, dan avg, metode pluck, serta penggabungan tabel dengan join. Setiap contoh dijalankan langsung pada aplikasi catatan dengan data deterministik, sehingga keluarannya dapat direproduksi ulang. Setelah membacanya, operasi baca, ubah massal, agregat, dan gabungan tabel dapat ditulis tanpa menyentuh model.
Kapan turun dari Eloquent#
Eloquent memetakan setiap baris menjadi objek model lengkap dengan event, timestamp, dan aturan $fillable. Pemetaan itu berguna untuk operasi per baris, tetapi membawa biaya yang tidak selalu dibutuhkan. Query builder menyusun SQL tanpa melewati model, sehingga hasilnya berupa objek baris sederhana tanpa perilaku tambahan. Kedua lapisan bukan saling menggantikan, sebab Eloquent sendiri dibangun di atas query builder dan keduanya boleh digunakan bersama pada satu aplikasi.
Penggunaan query builder paling tepat pada tiga kondisi. Pertama, operasi massal yang mengubah atau menghapus banyak baris sekaligus tanpa perlu memuat tiap model ke memori. Kedua, perhitungan agregat yang hasilnya satu angka, seperti jumlah atau rata-rata. Ketiga, membaca atau menggabungkan tabel yang tidak punya model maupun relasi Eloquent. Aksesnya melalui facade DB, misalnya DB::table("notes") yang menunjuk tabel notes.
Membaca baris dengan DB::table()#
Metode get() pada query builder menjalankan query dan mengembalikan collection berisi objek baris. Berbeda dengan Eloquent, objek tersebut adalah stdClass biasa yang propertinya bernama sama dengan kolom tabel, sehingga nilainya dibaca langsung tanpa konversi ke model. Blok berikut menyiapkan enam catatan deterministik pada kategori Arsip, lalu membacanya melalui query builder.
# menyiapkan data uji lalu membaca baris dengan DB::table()
cd ~/aplikasi-catatan
php artisan tinker --execute='
use App\Models\Category;
use App\Models\Note;
use Illuminate\Support\Facades\DB;
$arsip = Category::firstOrCreate(["nama" => "Arsip"]);
Note::where("category_id", $arsip->id)->delete();
foreach (["Rencana mingguan", "Daftar tugas", "Notulen rapat", "Ide proyek", "Catatan belanja", "Ringkasan bacaan"] as $j) {
Note::create(["judul" => $j, "isi" => "Isi catatan: " . $j . ".", "category_id" => $arsip->id]);
}
$baris = DB::table("notes")->where("category_id", $arsip->id)->orderBy("judul")->get();
echo get_class($baris->first()) . PHP_EOL;
foreach ($baris as $b) {
echo $b->judul . PHP_EOL;
}
'stdClass
Catatan belanja
Daftar tugas
Ide proyek
Notulen rapat
Rencana mingguan
Ringkasan bacaanKeluaran mengonfirmasi bahwa tiap baris adalah stdClass, bukan objek App\Models\Note. Properti judul tetap terbaca karena namanya sama dengan kolomnya. Urutan daftar mengikuti orderBy("judul") yang dijalankan di server database, sehingga tidak diperlukan pengurutan ulang di sisi PHP.
Operasi massal dengan update()#
Metode update() menuliskan perubahan ke seluruh baris hasil query dalam satu pernyataan UPDATE dan mengembalikan jumlah baris yang berubah. Cara ini jauh lebih hemat dibandingkan mengambil kumpulan model lalu memanggil save() pada setiap objek, karena versi Eloquent mengirim satu pernyataan UPDATE per baris. Metode delete() bekerja dengan pola yang sama untuk penghapusan massal.
# mengubah banyak baris sekaligus tanpa memuat model
cd ~/aplikasi-catatan
php artisan tinker --execute='
use App\Models\Category;
use App\Models\Note;
use Illuminate\Support\Facades\DB;
$arsip = Category::firstOrCreate(["nama" => "Arsip"]);
Note::where("category_id", $arsip->id)->delete();
foreach (["Rencana mingguan", "Daftar tugas", "Notulen rapat", "Ide proyek", "Catatan belanja", "Ringkasan bacaan"] as $j) {
Note::create(["judul" => $j, "isi" => "Isi catatan: " . $j . ".", "category_id" => $arsip->id]);
}
$terubah = DB::table("notes")->where("category_id", $arsip->id)->update(["isi" => "Isi diganti sekaligus."]);
echo "Baris terubah: " . $terubah . PHP_EOL;
'Baris terubah: 6Pola yang setara pada Eloquent jauh lebih panjang, yaitu mengambil collection lalu memanggil save() di dalam perulangan seperti berikut.
// tidak-dijalankan
$semua = Note::where("category_id", $idArsip)->get();
foreach ($semua as $n) {
$n->isi = "Isi diganti sekaligus.";
$n->save();
}Klausa where, whereIn, dan whereBetween#
Klausa penyaringan pada query builder sama bentuknya dengan yang digunakan Eloquent. where("kolom", nilai) menyaring kesamaan, sedangkan bentuk tiga argumen seperti where("kolom", ">", nilai) menyaring dengan operator pembanding lain. whereIn("kolom", array) menyaring keanggotaan pada daftar nilai, dan whereBetween("kolom", [batasBawah, batasAtas]) menyaring rentang nilai. Kolom pada klausa-klausa itu juga boleh berupa ekspresi SQL yang disusun melalui DB::raw(). Contoh berikut menggunakan ketiganya, dengan whereBetween menyasar panjang judul sebagai rentangnya.
# mencoba where, whereIn, dan whereBetween pada data Arsip
cd ~/aplikasi-catatan
php artisan tinker --execute='
use App\Models\Category;
use App\Models\Note;
use Illuminate\Support\Facades\DB;
$arsip = Category::firstOrCreate(["nama" => "Arsip"]);
Note::where("category_id", $arsip->id)->delete();
foreach (["Rencana mingguan", "Daftar tugas", "Notulen rapat", "Ide proyek", "Catatan belanja", "Ringkasan bacaan"] as $j) {
Note::create(["judul" => $j, "isi" => "Isi catatan: " . $j . ".", "category_id" => $arsip->id]);
}
echo DB::table("notes")->where("category_id", $arsip->id)->where("judul", "Rencana mingguan")->value("isi") . PHP_EOL;
echo DB::table("notes")->where("category_id", $arsip->id)->whereIn("judul", ["Daftar tugas", "Ide proyek", "Rencana mingguan"])->count() . PHP_EOL;
echo DB::table("notes")->where("category_id", $arsip->id)->whereBetween(DB::raw("length(judul)"), [10, 13])->count() . PHP_EOL;
'Isi catatan: Rencana mingguan.
3
3Baris pertama memperlihatkan where() tunggal yang diakhiri value("isi"), yaitu metode yang mengambil satu nilai kolom dari baris pertama hasil query. Baris kedua menghitung tiga judul yang tercantum pada whereIn(). Baris ketiga menghitung tiga judul dengan panjang antara 10 dan 13 karakter, yaitu “Daftar tugas”, “Ide proyek”, dan “Notulen rapat”.
Mengurutkan, membatasi, dan pluck#
Metode orderBy() menerima nama kolom dan arah pengurutan, dengan asc sebagai bawaan, lalu limit() membatasi jumlah baris yang dikembalikan. Untuk kebutuhan satu kolom saja, metode pluck("kolom") lebih ringan daripada get() karena hanya mengembalikan collection berisi nilai kolom tersebut tanpa sisa kolom lain. Bentuk keluarannya cocok untuk daftar pilihan seperti dropdown.
# mengurutkan, membatasi, lalu mengambil satu kolom dengan pluck
cd ~/aplikasi-catatan
php artisan tinker --execute='
use App\Models\Category;
use App\Models\Note;
use Illuminate\Support\Facades\DB;
$arsip = Category::firstOrCreate(["nama" => "Arsip"]);
Note::where("category_id", $arsip->id)->delete();
foreach (["Rencana mingguan", "Daftar tugas", "Notulen rapat", "Ide proyek", "Catatan belanja", "Ringkasan bacaan"] as $j) {
Note::create(["judul" => $j, "isi" => "Isi catatan: " . $j . ".", "category_id" => $arsip->id]);
}
$daftar = DB::table("notes")->where("category_id", $arsip->id)->orderBy("judul")->limit(3)->pluck("judul");
echo $daftar->implode(" | ") . PHP_EOL;
echo get_class($daftar) . PHP_EOL;
'Catatan belanja | Daftar tugas | Ide proyek
Illuminate\Support\Collectionpluck() mengembalikan Illuminate\Support\Collection berisi nilai biasa, bukan collection berisi model. Perbedaan itu penting karena metode pemrosesannya menerima nilai sederhana, misalnya implode() pada contoh di atas, dan tidak ada properti model yang dapat diakses dari isinya.
Agregat count, max, dan avg#
Agregat dihitung oleh server database dan hasilnya satu angka, sehingga metode agregat tidak perlu memuat baris apa pun ke aplikasi. Metode count() menghitung jumlah baris, max() mengembalikan nilai terbesar pada satu kolom, dan avg() mengembalikan rata-ratanya. Kedua metode terakhir menerima nama kolom biasa maupun ekspresi dari DB::raw(), seperti length(judul) untuk panjang teks judul.
# menghitung agregat pada catatan Arsip
cd ~/aplikasi-catatan
php artisan tinker --execute='
use App\Models\Category;
use App\Models\Note;
use Illuminate\Support\Facades\DB;
$arsip = Category::firstOrCreate(["nama" => "Arsip"]);
Note::where("category_id", $arsip->id)->delete();
foreach (["Rencana mingguan", "Daftar tugas", "Notulen rapat", "Ide proyek", "Catatan belanja", "Ringkasan bacaan"] as $j) {
Note::create(["judul" => $j, "isi" => "Isi catatan: " . $j . ".", "category_id" => $arsip->id]);
}
echo DB::table("notes")->where("category_id", $arsip->id)->count() . PHP_EOL;
echo DB::table("notes")->where("category_id", $arsip->id)->max(DB::raw("length(judul)")) . PHP_EOL;
echo round(DB::table("notes")->where("category_id", $arsip->id)->avg(DB::raw("length(judul)")), 1) . PHP_EOL;
'6
16
13.7Keluaran memperlihatkan enam baris, judul terpanjang 16 karakter, dan rata-rata panjang judul 13,7 setelah pembulatan ke satu angka desimal dengan round(). Angka tersebut dihitung oleh SQLite pada setiap eksekusi, sehingga hasilnya selalu sama selama data ujinya sama.
Menggabungkan tabel dengan join#
Metode join() menggabungkan dua tabel melalui kondisi kolom penghubung tanpa memerlukan relasi Eloquent. Argumennya berurutan, yaitu nama tabel kedua, kolom kiri, operator, dan kolom kanan. Kolom yang diambil ditentukan oleh get() dengan array nama kolom yang diberi awalan nama tabel, dan where() setelahnya dapat menyaring kolom dari tabel mana pun.
# menggabungkan notes dan categories dengan join
cd ~/aplikasi-catatan
php artisan tinker --execute='
use App\Models\Category;
use App\Models\Note;
use Illuminate\Support\Facades\DB;
$arsip = Category::firstOrCreate(["nama" => "Arsip"]);
Note::where("category_id", $arsip->id)->delete();
foreach (["Rencana mingguan", "Daftar tugas", "Notulen rapat", "Ide proyek", "Catatan belanja", "Ringkasan bacaan"] as $j) {
Note::create(["judul" => $j, "isi" => "Isi catatan: " . $j . ".", "category_id" => $arsip->id]);
}
$gabungan = DB::table("notes")
->join("categories", "categories.id", "=", "notes.category_id")
->where("categories.nama", "Arsip")
->orderBy("notes.judul")
->get(["notes.judul", "categories.nama"]);
foreach ($gabungan as $g) {
echo $g->judul . " - " . $g->nama . PHP_EOL;
}
'Catatan belanja - Arsip
Daftar tugas - Arsip
Ide proyek - Arsip
Notulen rapat - Arsip
Rencana mingguan - Arsip
Ringkasan bacaan - ArsipSetiap baris hasil berisi kolom dari kedua tabel, yaitu judul dari notes dan nama dari categories. Pada tabel yang tidak memiliki model, join semacam ini adalah satu-satunya cara menggabungkan data, sedangkan pada tabel yang memiliki model, pilihan tetap ada antara join dan relasi Eloquent.
Kesalahan umum#
Kesalahan pertama adalah mengakses properti relasi pada hasil query builder, misalnya $b->category pada baris stdClass. Baris tersebut bukan model sehingga tidak punya relasi, dan PHP menampilkan warning “Undefined property: stdClass::$category”. Penggabungan datanya dilakukan dengan join() seperti pada bagian sebelumnya, atau barisnya dibaca ulang melalui Eloquent apabila relasinya dibutuhkan.
Kesalahan kedua adalah memanggil metode model pada baris query builder, misalnya $b->save(). Pemanggilan itu gagal dengan error “Call to undefined method stdClass::save()” karena stdClass tidak memiliki metode apa pun dari Eloquent. Perubahan data melalui query builder dituliskan dengan update() langsung pada query.
Kesalahan ketiga adalah menjalankan agregat tanpa klausa where pada tabel yang digunakan banyak topik. Pemanggilan DB::table("notes")->count() menghitung seluruh baris di tabel, bukan hanya data yang sedang dianalisis, sehingga angkanya berubah setiap kali data lain bertambah. Agregat hampir selalu perlu kondisi yang sama dengan query baca yang menghasilkan daftarnya.
Ringkasan#
- Query builder menyusun SQL tanpa melewati model dan paling tepat untuk operasi massal, agregat, serta tabel tanpa model, dengan akses melalui
DB::table(). - Hasil
get()berupa collection barisstdClass, sedangkanpluck()mengembalikan collection nilai satu kolom dan agregatcount,max,avgmengembalikan satu angka. update()dandelete()menjalankan pernyataan massal dalam satu kirim, tetapi tanpawherekeduanya memproses seluruh baris tabel.join()menggabungkan dua tabel melalui kondisi kolom penghubung dan menjadi cara utama menangani tabel yang tidak memiliki model.
Referensi#
- Database: Query Builder — Laravel — Dokumentasi resmi klausa where, orderBy, agregat, pluck, dan join.
- Database: Getting Started — Laravel — Dokumentasi konfigurasi koneksi database dan pemakaian facade DB.