Query SQL Lanjutan: GROUP BY, HAVING, dan Subquery
Pada Bab 5, kita sudah berhasil menghubungkan tabel pelanggan, produk, pesanan, dan detail_pesanan menggunakan foreign key dan JOIN.
Sekarang database toko online kita sudah bisa menyimpan informasi pelanggan, produk, dan transaksi.
Tapi sebagai pemilik toko, tentu kita ingin mengetahui lebih banyak hal.
Misalnya:
- Berapa total omzet penjualan?
- Produk apa yang paling banyak terjual?
- Siapa pelanggan dengan total pembelian terbesar?
- Berapa jumlah pesanan yang dibuat setiap pelanggan?
- Produk mana yang harganya di atas rata-rata?
Pertanyaan seperti itu tidak cukup dijawab hanya dengan mengambil seluruh isi tabel.
Kita membutuhkan query yang dapat menghitung, mengelompokkan, menyaring hasil perhitungan, dan menggunakan hasil query lain sebagai bagian dari kondisi.
Pada Bab 6 Series Database BapakGabut ini, kita akan mempelajari tiga konsep utama:
GROUP BYuntuk mengelompokkan data.HAVINGuntuk memfilter hasil pengelompokan.- Subquery untuk menggunakan hasil sebuah query di dalam query lainnya.
Kita juga akan mengenal fungsi agregasi seperti COUNT, SUM, AVG, MIN, dan MAX.
Tenang, kita tidak sedang membuat sistem analitik perusahaan multinasional.
Kita masih mengurus toko online BapakGabut. Hanya saja, sekarang pemilik tokonya mulai ingin melihat laporan.
Persiapan: Database dari Bab Sebelumnya
Pastikan MySQL Server berjalan dan DBeaver sudah terhubung.
Buka SQL Editor, kemudian pilih database:
USE db_toko_bapakgabut;Database kita memiliki empat tabel:
| Tabel | Fungsi |
|---|---|
pelanggan | Menyimpan data pelanggan |
produk | Menyimpan data produk |
pesanan | Menyimpan informasi utama pesanan |
detail_pesanan | Menyimpan produk dan jumlah dalam setiap pesanan |
Relasinya:
pelanggan (1) --- (N) pesananpesanan (1) --- (N) detail_pesananproduk (1) --- (N) detail_pesananPada Bab 5, kita sudah memasukkan contoh transaksi:
- Andi membeli 1 Keyboard Mechanical seharga Rp325.000.
- Andi membeli 2 Mouse Wireless seharga Rp135.000 per unit.
- Budi membeli 1 Monitor 24 Inch seharga Rp1.650.000.
Jika seluruh data latihan masih sesuai contoh, total pembeliannya adalah:
| Pelanggan | Total Pembelian |
|---|---|
| Andi | Rp595.000 |
| Budi | Rp1.650.000 |
Total nilai transaksi dari dua pesanan tersebut adalah Rp2.245.000.
Namun, hasil query di komputer kamu bisa berbeda jika sudah menambahkan, mengubah, atau menghapus data.
Contoh angka dalam artikel ini merupakan ilustrasi berdasarkan transaksi yang dibuat pada Bab 5, bukan hasil yang dijamin sama untuk setiap database.
Memeriksa Data Sebelum Membuat Laporan
Coba jalankan:
SELECT p.id_pesanan, pl.nama_pelanggan, pr.nama_produk, dp.jumlah, dp.harga_satuan, dp.jumlah * dp.harga_satuan AS subtotalFROM pesanan pINNER JOIN pelanggan pl ON p.id_pelanggan = pl.id_pelangganINNER JOIN detail_pesanan dp ON p.id_pesanan = dp.id_pesananINNER JOIN produk pr ON dp.id_produk = pr.id_produkORDER BY p.id_pesanan, dp.id_detail;Query tersebut menggabungkan empat tabel untuk menampilkan detail pembelian.
Kita akan menggunakan data yang sama untuk berbagai latihan analisis.
Mengenal Fungsi Agregasi SQL
Fungsi agregasi digunakan untuk menghasilkan nilai ringkasan dari sekumpulan baris.
Beberapa fungsi agregasi yang sering digunakan dalam MySQL adalah:
| Fungsi | Kegunaan |
|---|---|
COUNT() | Menghitung jumlah baris atau nilai non-NULL |
SUM() | Menjumlahkan nilai |
AVG() | Menghitung rata-rata |
MIN() | Mengambil nilai terkecil |
MAX() | Mengambil nilai terbesar |
Mari kita praktikkan satu per satu.
COUNT — Menghitung Jumlah Data
Misalnya, kita ingin mengetahui jumlah produk yang tersedia dalam tabel.
SELECT COUNT(*) AS jumlah_produkFROM produk;COUNT(*) menghitung jumlah baris pada tabel, termasuk baris yang memiliki nilai NULL pada kolom tertentu.
Jika tabel produk memiliki empat baris setelah latihan Bab 4, hasilnya adalah 4.
Tetapi jika kamu menambahkan produk lain, jumlahnya tentu berbeda.
Sekarang coba menghitung jumlah pelanggan:
SELECT COUNT(*) AS jumlah_pelangganFROM pelanggan;Kita juga bisa menghitung jumlah pesanan:
SELECT COUNT(*) AS jumlah_pesananFROM pesanan;Perhatikan perbedaan berikut:
SELECT COUNT(*) AS total_baris, COUNT(email) AS email_tidak_nullFROM pelanggan;COUNT(*) menghitung semua baris.
Sementara COUNT(email) hanya menghitung baris dengan nilai email yang tidak NULL.
Karena pada struktur tabel pelanggan kita kolom email menggunakan NOT NULL, kedua hasil tersebut seharusnya sama.
SUM — Menjumlahkan Nilai
Misalnya, kita ingin menghitung total stok seluruh produk.
SELECT SUM(stok) AS total_stokFROM produk;MySQL akan menjumlahkan nilai stok dari semua produk.
Sekarang coba menghitung total nilai seluruh detail transaksi:
SELECT SUM(jumlah * harga_satuan) AS total_penjualanFROM detail_pesanan;Jika hanya ada tiga baris detail transaksi dari Bab 5, hasilnya:
2245000.00Perhitungannya:
325000 + (2 × 135000) + 1650000 = 2245000Nilai tersebut merupakan total nominal detail pesanan yang tercatat.
Kita menyebutnya total penjualan untuk latihan ini, tetapi pada aplikasi nyata, definisi omzet perlu mempertimbangkan status pesanan, pembatalan, retur, diskon, pajak, dan biaya lainnya.
Jangan langsung menganggap semua pesanan yang tercatat sudah menjadi pendapatan final.
AVG — Menghitung Rata-Rata
Untuk menghitung rata-rata harga produk:
SELECT AVG(harga) AS rata_rata_hargaFROM produk;AVG() menghitung rata-rata dari nilai yang tidak NULL.
Contoh lain, rata-rata jumlah unit per baris detail pesanan:
SELECT AVG(jumlah) AS rata_rata_jumlahFROM detail_pesanan;Perlu diperhatikan, query tersebut menghitung rata-rata jumlah unit per baris detail, bukan rata-rata jumlah barang per pesanan.
Kedua pertanyaan tersebut berbeda dan dapat menghasilkan nilai yang berbeda.
MIN dan MAX — Nilai Terkecil dan Terbesar
Untuk mencari harga produk termurah dan termahal:
SELECT MIN(harga) AS harga_termurah, MAX(harga) AS harga_termahalFROM produk;Query tersebut hanya mengembalikan nilai harga, bukan nama produknya.
Kalau ingin mengetahui produk yang memiliki harga tersebut, kita perlu query tambahan atau menggunakan subquery yang akan dipelajari nanti.
GROUP BY — Mengelompokkan Data
Sekarang kita masuk ke konsep utama pertama.
Bayangkan tabel detail_pesanan berisi banyak baris transaksi.
Kita ingin mengetahui berapa unit masing-masing produk yang sudah terjual.
Jika menggunakan SUM(jumlah) tanpa pengelompokan, kita hanya mendapatkan total seluruh produk.
Padahal, kita ingin hasil per produk.
Di sinilah GROUP BY digunakan.
Menghitung Total Unit Terjual per Produk
Jalankan:
SELECT pr.id_produk, pr.nama_produk, SUM(dp.jumlah) AS total_terjualFROM detail_pesanan dpINNER JOIN produk pr ON dp.id_produk = pr.id_produkGROUP BY pr.id_produk, pr.nama_produkORDER BY total_terjual DESC;Query tersebut melakukan beberapa hal:
- Menggabungkan detail pesanan dengan produk.
- Mengelompokkan baris berdasarkan produk.
- Menjumlahkan jumlah pembelian pada setiap kelompok.
- Mengurutkan hasil berdasarkan total unit terjual.
Jika menggunakan data contoh Bab 5, hasilnya:
| Produk | Total Terjual |
|---|---|
| Mouse Wireless | 2 |
| Keyboard Mechanical | 1 |
| Monitor 24 Inch | 1 |
Sekarang kita tahu bahwa Mouse Wireless menjadi produk dengan unit penjualan terbanyak dalam data latihan.
Namun, produk yang paling banyak terjual belum tentu menghasilkan nilai penjualan terbesar.
Harga produk juga berpengaruh.
Menghitung Nilai Penjualan per Produk
Coba jalankan:
SELECT pr.id_produk, pr.nama_produk, SUM(dp.jumlah) AS total_unit, SUM(dp.jumlah * dp.harga_satuan) AS total_nilaiFROM detail_pesanan dpINNER JOIN produk pr ON dp.id_produk = pr.id_produkGROUP BY pr.id_produk, pr.nama_produkORDER BY total_nilai DESC;Hasil ilustrasinya:
| Produk | Total Unit | Total Nilai |
|---|---|---|
| Monitor 24 Inch | 1 | 1650000 |
| Keyboard Mechanical | 1 | 325000 |
| Mouse Wireless | 2 | 270000 |
Menarik, bukan?
Mouse Wireless paling banyak terjual berdasarkan jumlah unit.
Tetapi Monitor 24 Inch menghasilkan nilai penjualan terbesar.
Jadi, ketika seseorang bertanya “produk terlaris”, kita perlu memastikan maksudnya:
- Paling banyak unit terjual?
- Paling tinggi nilai penjualan?
- Paling sering muncul dalam pesanan?
Definisi metrik yang berbeda dapat menghasilkan peringkat yang berbeda.
Menghitung Total Pembelian per Pelanggan
Sekarang kita ingin mengetahui total nilai pembelian setiap pelanggan.
SELECT pl.id_pelanggan, pl.nama_pelanggan, SUM(dp.jumlah * dp.harga_satuan) AS total_pembelianFROM pelanggan plINNER JOIN pesanan p ON pl.id_pelanggan = p.id_pelangganINNER JOIN detail_pesanan dp ON p.id_pesanan = dp.id_pesananGROUP BY pl.id_pelanggan, pl.nama_pelangganORDER BY total_pembelian DESC;Jika data sesuai contoh, Budi berada di atas Andi karena total nilai pembeliannya lebih besar.
Query tersebut hanya menampilkan pelanggan yang memiliki detail transaksi.
Pelanggan yang belum melakukan pembelian tidak akan muncul karena kita menggunakan INNER JOIN.
GROUP BY dengan LEFT JOIN
Bagaimana jika kita ingin menampilkan semua pelanggan, termasuk yang belum pernah membeli?
Gunakan LEFT JOIN.
SELECT pl.id_pelanggan, pl.nama_pelanggan, COUNT(DISTINCT p.id_pesanan) AS jumlah_pesanan, COALESCE( SUM(dp.jumlah * dp.harga_satuan), 0 ) AS total_pembelianFROM pelanggan plLEFT JOIN pesanan p ON pl.id_pelanggan = p.id_pelangganLEFT JOIN detail_pesanan dp ON p.id_pesanan = dp.id_pesananGROUP BY pl.id_pelanggan, pl.nama_pelangganORDER BY total_pembelian DESC;Ada dua hal penting pada query tersebut.
Kenapa Menggunakan COUNT(DISTINCT)?
Karena satu pesanan dapat memiliki beberapa baris detail.
Jika kita menggunakan COUNT(p.id_pesanan) setelah JOIN dengan detail_pesanan, satu pesanan yang memiliki dua produk bisa ikut terhitung dua kali.
Dengan COUNT(DISTINCT p.id_pesanan), kita menghitung ID pesanan yang berbeda.
Apa Fungsi COALESCE?
COALESCE() mengembalikan nilai pertama yang tidak NULL dari daftar argumennya.
Pada query ini:
COALESCE(SUM(dp.jumlah * dp.harga_satuan), 0)Jika pelanggan belum mempunyai detail pembelian dan hasil SUM() adalah NULL, nilai tersebut ditampilkan sebagai 0.
Dengan begitu, pelanggan tanpa pembelian tetap muncul dengan total nol.
WHERE dan HAVING: Apa Bedanya?
Sebelum mengenal HAVING, kita sudah menggunakan WHERE untuk memfilter data.
Contohnya:
SELECT *FROM produkWHERE harga > 200000;Query tersebut memfilter baris produk berdasarkan harga.
Tetapi bagaimana jika kita ingin menampilkan pelanggan dengan total pembelian di atas Rp500.000?
Total pembelian tersebut baru diketahui setelah kita melakukan pengelompokan dan perhitungan SUM().
Untuk memfilter hasil agregasi, kita menggunakan HAVING.
Menggunakan HAVING
Jalankan:
SELECT pl.id_pelanggan, pl.nama_pelanggan, SUM(dp.jumlah * dp.harga_satuan) AS total_pembelianFROM pelanggan plINNER JOIN pesanan p ON pl.id_pelanggan = p.id_pelangganINNER JOIN detail_pesanan dp ON p.id_pesanan = dp.id_pesananGROUP BY pl.id_pelanggan, pl.nama_pelangganHAVING SUM(dp.jumlah * dp.harga_satuan) > 500000ORDER BY total_pembelian DESC;Query tersebut hanya menampilkan pelanggan dengan total pembelian lebih dari Rp500.000.
Pada data contoh, Andi dan Budi sama-sama memenuhi kondisi.
Perbedaan WHERE dan HAVING
| WHERE | HAVING |
|---|---|
| Memfilter baris sebelum pengelompokan | Memfilter kelompok setelah agregasi |
| Umumnya menggunakan kondisi pada nilai baris | Dapat menggunakan kondisi fungsi agregasi |
| Digunakan sebelum GROUP BY dalam penulisan query | Digunakan setelah GROUP BY |
Secara konseptual, prosesnya dapat dibayangkan seperti ini:
FROM / JOIN | WHERE | GROUP BY | HAVING | SELECT | ORDER BY | LIMITUrutan tersebut merupakan gambaran logis untuk memahami query, bukan urutan fisik yang selalu digunakan oleh mesin database.
MySQL dapat mengoptimalkan pelaksanaan query dengan strategi yang berbeda.
Menggabungkan WHERE dan HAVING
Misalnya, kita hanya ingin menghitung pesanan berstatus selesai, lalu menampilkan pelanggan dengan total pembelian lebih dari Rp500.000.
SELECT pl.id_pelanggan, pl.nama_pelanggan, SUM(dp.jumlah * dp.harga_satuan) AS total_pembelianFROM pelanggan plINNER JOIN pesanan p ON pl.id_pelanggan = p.id_pelangganINNER JOIN detail_pesanan dp ON p.id_pesanan = dp.id_pesananWHERE p.status_pesanan = 'selesai'GROUP BY pl.id_pelanggan, pl.nama_pelangganHAVING SUM(dp.jumlah * dp.harga_satuan) > 500000;WHERE memilih baris pesanan yang berstatus selesai.
Setelah itu, GROUP BY mengelompokkan data berdasarkan pelanggan.
Terakhir, HAVING memfilter kelompok berdasarkan total pembelian.
Catatan: Pada Bab 5, pesanan contoh kita menggunakan status baru. Jadi, query di atas bisa menghasilkan nol baris jika belum ada pesanan berstatus selesai.
Hal tersebut bukan berarti query salah.
Hasilnya memang mengikuti kondisi data.
Untuk mencoba dengan data contoh tanpa mengubah status pesanan, kamu dapat mengganti 'selesai' menjadi 'baru'.
Mengenal Subquery
Sekarang kita masuk ke konsep utama berikutnya.
Subquery adalah query yang ditulis di dalam query lain.
Hasil subquery dapat digunakan oleh query luar untuk melakukan perbandingan, pemfilteran, atau membentuk sumber data sementara.
Misalnya, kita ingin mencari produk yang harganya lebih tinggi daripada rata-rata harga seluruh produk.
Kita bisa memecah pertanyaannya menjadi dua:
- Berapa rata-rata harga produk?
- Produk apa saja yang harganya lebih tinggi dari nilai tersebut?
Subquery memungkinkan kita menggabungkan kedua langkah itu dalam satu perintah SQL.
Subquery dengan Nilai Tunggal
Pertama, lihat query untuk menghitung rata-rata harga:
SELECT AVG(harga)FROM produk;Sekarang gunakan query tersebut sebagai bagian dari kondisi:
SELECT id_produk, nama_produk, hargaFROM produkWHERE harga > ( SELECT AVG(harga) FROM produk)ORDER BY harga DESC;Bagian di dalam tanda kurung merupakan subquery.
Subquery menghitung rata-rata harga produk.
Kemudian query luar mengambil produk yang harganya lebih besar dari rata-rata tersebut.
Jenis ini sering disebut scalar subquery karena diharapkan menghasilkan satu nilai.
Jika subquery yang digunakan dalam konteks nilai tunggal menghasilkan lebih dari satu baris, MySQL akan menampilkan error.
Subquery dengan IN
Subquery juga dapat menghasilkan beberapa baris.
Misalnya, kita ingin mengetahui pelanggan yang pernah membuat pesanan.
SELECT id_pelanggan, nama_pelangganFROM pelangganWHERE id_pelanggan IN ( SELECT id_pelanggan FROM pesanan);Subquery mengambil daftar ID pelanggan dari tabel pesanan.
Kemudian query luar mencari pelanggan yang ID-nya terdapat dalam daftar tersebut.
Perintah IN digunakan untuk memeriksa apakah suatu nilai termasuk dalam kumpulan nilai.
Query seperti ini juga dapat ditulis menggunakan JOIN atau EXISTS.
Pilihan terbaik bergantung pada kebutuhan, bentuk data, dan rencana eksekusi database.
Subquery dengan EXISTS
EXISTS digunakan untuk memeriksa apakah sebuah subquery menghasilkan setidaknya satu baris.
Misalnya, kita ingin menampilkan pelanggan yang sudah pernah membuat pesanan.
SELECT pl.id_pelanggan, pl.nama_pelangganFROM pelanggan plWHERE EXISTS ( SELECT 1 FROM pesanan p WHERE p.id_pelanggan = pl.id_pelanggan);Subquery tersebut mengacu pada kolom dari query luar, yaitu pl.id_pelanggan.
Jenis seperti ini disebut correlated subquery.
Untuk setiap pelanggan yang diperiksa secara logis, kondisi EXISTS menentukan apakah ada pesanan yang cocok.
SELECT 1 digunakan karena EXISTS hanya membutuhkan informasi apakah baris ditemukan, bukan isi kolom yang dipilih.
MySQL dapat mengoptimalkan query EXISTS sehingga cara eksekusi fisiknya tidak selalu sama dengan gambaran evaluasi baris demi baris.
Mencari Pelanggan yang Belum Pernah Memesan
Kita bisa menggunakan NOT EXISTS.
SELECT pl.id_pelanggan, pl.nama_pelangganFROM pelanggan plWHERE NOT EXISTS ( SELECT 1 FROM pesanan p WHERE p.id_pelanggan = pl.id_pelanggan);Query tersebut menampilkan pelanggan yang tidak memiliki pesanan.
Ini merupakan alternatif dari pola LEFT JOIN ... IS NULL yang sudah kita pelajari pada Bab 5.
Subquery pada FROM: Derived Table
Selain digunakan dalam WHERE, subquery juga dapat digunakan sebagai sumber data pada bagian FROM.
Contohnya, kita ingin menghitung rata-rata nilai pesanan.
Langkah pertama adalah menghitung total setiap pesanan.
SELECT id_pesanan, SUM(jumlah * harga_satuan) AS total_pesananFROM detail_pesananGROUP BY id_pesanan;Setelah itu, kita menghitung rata-rata dari total-total tersebut.
SELECT AVG(ringkasan.total_pesanan) AS rata_rata_nilai_pesananFROM ( SELECT id_pesanan, SUM(jumlah * harga_satuan) AS total_pesanan FROM detail_pesanan GROUP BY id_pesanan) AS ringkasan;Subquery pada FROM tersebut disebut derived table.
MySQL memperlakukannya sebagai hasil tabel yang dapat digunakan oleh query luar.
Perhatikan alias ringkasan.
Pada MySQL, derived table harus mempunyai alias.
Jika hanya terdapat dua pesanan contoh dari Bab 5, totalnya adalah Rp595.000 dan Rp1.650.000.
Maka rata-ratanya:
(595000 + 1650000) / 2 = 1122500Jadi, rata-rata nilai dua pesanan tersebut adalah Rp1.122.500.
Perlu diperhatikan, query ini hanya menghitung pesanan yang memiliki baris detail.
Pesanan yang belum mempunyai detail tidak ikut dihitung.
JOIN atau Subquery, Mana yang Lebih Baik?
Setelah mengenal subquery, mungkin muncul pertanyaan:
Kalau JOIN sudah bisa menggabungkan tabel, kenapa kita masih membutuhkan subquery?
Karena keduanya memiliki kegunaan yang saling melengkapi.
JOIN cocok ketika kita perlu menggabungkan informasi dari beberapa tabel.
Subquery sering berguna ketika kita ingin menggunakan hasil suatu perhitungan atau pencarian sebagai bagian dari query lain.
Contohnya:
| Kebutuhan | Pendekatan yang Bisa Digunakan |
|---|---|
| Menampilkan nama pelanggan dan pesanannya | JOIN |
| Mencari produk di atas rata-rata harga | Subquery |
| Mencari pelanggan yang pernah memesan | JOIN, IN, atau EXISTS |
| Menghitung rata-rata dari total setiap pesanan | Derived table atau pendekatan agregasi bertahap |
Tidak ada aturan bahwa subquery selalu lebih lambat daripada JOIN, atau sebaliknya.
Performa bergantung pada struktur tabel, jumlah data, index, kondisi query, dan optimasi MySQL.
Pada Bab 7, kita akan mempelajari bagaimana index dan EXPLAIN membantu memahami performa query.
Kesalahan Umum Saat Menggunakan GROUP BY dan Subquery
Ada beberapa kesalahan yang sering terjadi ketika query mulai kompleks.
1. Memilih Kolom yang Tidak Sesuai dengan GROUP BY
Perhatikan contoh:
SELECT id_pelanggan, status_pesanan, COUNT(*)FROM pesananGROUP BY id_pelanggan;Query tersebut bermasalah secara konsep karena satu pelanggan bisa mempunyai beberapa pesanan dengan status berbeda.
Kolom status_pesanan tidak memiliki satu nilai yang pasti untuk setiap kelompok pelanggan.
Pada MySQL dengan ONLY_FULL_GROUP_BY aktif, query seperti ini dapat ditolak jika kolom tersebut tidak bergantung secara fungsional pada kolom pengelompokan.
Jika kita ingin menghitung jumlah pesanan berdasarkan pelanggan dan status, gunakan:
SELECT id_pelanggan, status_pesanan, COUNT(*) AS jumlah_pesananFROM pesananGROUP BY id_pelanggan, status_pesanan;Dengan begitu, pengelompokannya menjadi jelas.
2. Menggunakan WHERE untuk Memfilter SUM
Contoh yang tidak tepat:
SELECT id_pesanan, SUM(jumlah * harga_satuan) AS totalFROM detail_pesananWHERE SUM(jumlah * harga_satuan) > 500000GROUP BY id_pesanan;Fungsi agregasi tidak digunakan secara langsung dalam kondisi WHERE pada tingkat query yang sama.
Gunakan HAVING:
SELECT id_pesanan, SUM(jumlah * harga_satuan) AS totalFROM detail_pesananGROUP BY id_pesananHAVING SUM(jumlah * harga_satuan) > 500000;3. Menganggap COUNT Selalu Menghitung Data Unik
Perhatikan:
SELECT COUNT(id_pesanan)FROM detail_pesanan;Query tersebut menghitung jumlah nilai id_pesanan yang tidak NULL pada baris detail.
Jika satu pesanan memiliki tiga baris detail, ID pesanan tersebut ikut dihitung tiga kali.
Untuk menghitung jumlah pesanan yang berbeda:
SELECT COUNT(DISTINCT id_pesanan) AS jumlah_pesananFROM detail_pesanan;4. Tidak Memahami Hasil NULL
Fungsi agregasi memiliki perilaku berbeda terhadap NULL.
COUNT(*) menghitung baris, sedangkan COUNT(kolom) mengabaikan nilai NULL.
SUM() dan AVG() juga mengabaikan nilai NULL dalam perhitungannya.
Jika tidak ada nilai yang dapat dijumlahkan, SUM() dapat menghasilkan NULL.
Gunakan COALESCE() ketika memang ingin menampilkan nilai pengganti, misalnya nol.
5. Menggunakan Subquery Tanpa Memahami Jumlah Hasilnya
Subquery yang digunakan bersama operator = harus menghasilkan paling banyak satu baris.
Jika subquery dapat menghasilkan banyak nilai, pertimbangkan menggunakan IN, EXISTS, atau pendekatan JOIN sesuai kebutuhan.
Memahami bentuk hasil subquery sama pentingnya dengan memahami sintaksnya.
Latihan Mandiri
Sekarang kita coba menggabungkan beberapa konsep dalam satu latihan.
Gunakan database db_toko_bapakgabut.
Latihan 1 — Jumlah Pesanan per Pelanggan
Tampilkan:
- Nama pelanggan.
- Jumlah pesanan.
Tampilkan semua pelanggan, termasuk yang belum pernah memesan.
Urutkan berdasarkan jumlah pesanan terbanyak.
Latihan 2 — Produk dengan Penjualan Lebih dari Satu Unit
Tampilkan:
- Nama produk.
- Total unit terjual.
Hanya tampilkan produk dengan total unit terjual lebih dari satu.
Gunakan GROUP BY dan HAVING.
Latihan 3 — Produk di Atas Rata-Rata Harga
Tampilkan nama dan harga produk yang harganya lebih besar dari rata-rata harga seluruh produk.
Gunakan subquery.
Latihan 4 — Pelanggan Tanpa Pesanan
Tampilkan pelanggan yang belum pernah membuat pesanan.
Gunakan NOT EXISTS.
Latihan 5 — Pesanan dengan Total di Atas Rp500.000
Tampilkan:
- ID pesanan.
- Total nilai pesanan.
Hanya tampilkan pesanan yang totalnya lebih dari Rp500.000.
Urutkan berdasarkan total terbesar.
Coba kerjakan sendiri sebelum melihat pembahasannya.
Pembahasan Latihan
Jawaban Latihan 1
SELECT pl.id_pelanggan, pl.nama_pelanggan, COUNT(p.id_pesanan) AS jumlah_pesananFROM pelanggan plLEFT JOIN pesanan p ON pl.id_pelanggan = p.id_pelangganGROUP BY pl.id_pelanggan, pl.nama_pelangganORDER BY jumlah_pesanan DESC, pl.nama_pelanggan ASC;Karena query ini hanya menggabungkan pelanggan dan pesanan, setiap baris pesanan dihitung satu kali.
Jika kita ikut menggabungkan detail pesanan, perlu memperhatikan potensi penghitungan ganda.
Jawaban Latihan 2
SELECT pr.id_produk, pr.nama_produk, SUM(dp.jumlah) AS total_terjualFROM produk prINNER JOIN detail_pesanan dp ON pr.id_produk = dp.id_produkGROUP BY pr.id_produk, pr.nama_produkHAVING SUM(dp.jumlah) > 1ORDER BY total_terjual DESC;Jawaban Latihan 3
SELECT id_produk, nama_produk, hargaFROM produkWHERE harga > ( SELECT AVG(harga) FROM produk)ORDER BY harga DESC;Jawaban Latihan 4
SELECT pl.id_pelanggan, pl.nama_pelangganFROM pelanggan plWHERE NOT EXISTS ( SELECT 1 FROM pesanan p WHERE p.id_pelanggan = pl.id_pelanggan);Jawaban Latihan 5
SELECT id_pesanan, SUM(jumlah * harga_satuan) AS total_pesananFROM detail_pesananGROUP BY id_pesananHAVING SUM(jumlah * harga_satuan) > 500000ORDER BY total_pesanan DESC;Jika data sesuai contoh Bab 5, kedua pesanan memenuhi kondisi tersebut.
Andi memiliki total Rp595.000 dan Budi Rp1.650.000.
Kesimpulan
Pada Bab 6 ini, kita sudah mempelajari cara mengubah data transaksi menjadi informasi yang lebih berguna.
Kita menggunakan fungsi agregasi untuk menghitung jumlah, total, rata-rata, nilai terkecil, dan nilai terbesar.
Dengan GROUP BY, kita dapat mengelompokkan transaksi berdasarkan produk, pelanggan, atau pesanan.
Dengan HAVING, kita dapat menyaring kelompok berdasarkan hasil agregasi.
Kita juga mempelajari subquery menggunakan nilai tunggal, IN, EXISTS, NOT EXISTS, dan derived table.
Sekarang kita sudah bisa menjawab berbagai pertanyaan bisnis menggunakan SQL.
Misalnya, produk mana yang paling banyak terjual, siapa pelanggan dengan nilai pembelian terbesar, dan berapa rata-rata nilai pesanan.
Tetapi ada tantangan berikutnya.
Bagaimana jika tabel produk berisi ratusan ribu baris dan tabel transaksi berisi jutaan data?
Apakah query yang kita tulis masih cukup cepat?
Dan bagaimana memastikan perubahan data tidak berhenti di tengah jalan sehingga stok dan transaksi menjadi tidak konsisten?
Pada Bab 7, kita akan mempelajari Index dan Transaction MySQL: Bikin Query Lebih Efisien dan Aman.
Kita akan mengenal index, membaca hasil EXPLAIN, serta menggunakan START TRANSACTION, COMMIT, dan ROLLBACK.
Karena membuat laporan penjualan itu penting.
Tapi memastikan query tidak membuat pengguna menunggu terlalu lama juga tidak kalah penting.
Sampai jumpa di artikel berikutnya, Gabuters!
Bagikan Artikel
Buat poster berbagi atau salin tautan artikel ini.
Lanjut membaca
Coba jalur lain
Pilihan konsisten dari artikel lain
Terakhir diperbarui pada , 20 hari yang lalu
Sebagian konten mungkin sudah tidak berlaku