Setelah tahu “berapa banyak”, laporan berikutnya hampir selalu menanyakan “berapa totalnya”: berapa unit kopi yang terjual sepanjang periode, berapa omzet keseluruhan, berapa total pendapatan dari satu kota saja. Fungsi agregasi SUM menjawab pertanyaan-pertanyaan itu dengan menjumlahkan nilai dari banyak baris menjadi satu angka. Pada chapter ini kita akan berlatih menjumlahkan kolom, menjumlahkan hasil ekspresi per baris, lalu menggabungkannya dengan WHERE. Bagian terpenting adalah satu gotcha yang sering menjengkelkan: SUM menghasilkan NULL — bukan 0 — ketika tidak ada satu baris pun yang cocok, dan kita akan belajar cara menanganinya.

Menyiapkan Tabel Latihan #

Kita pakai kembali pola database latihan belajar_agregasi dengan tabel penjualan dua belas baris yang sama seperti chapter COUNT, sehingga angka-angka yang muncul mudah dibandingkan:

sql
DROP DATABASE IF EXISTS belajar_agregasi;
CREATE DATABASE belajar_agregasi;
USE belajar_agregasi;

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Database changed

Catatan 1 warning pada baris pertama muncul karena database belajar_agregasi memang belum ada saat DROP DATABASE IF EXISTS dijalankan; kombinasi kedua perintah tersebut membuat skrip latihan selalu aman dijalankan berulang kali dari keadaan apa pun.

sql
CREATE TABLE penjualan (
  id INT AUTO_INCREMENT PRIMARY KEY,
  tanggal DATE NOT NULL,
  kota VARCHAR(50) NOT NULL,
  produk VARCHAR(50) NOT NULL,
  jumlah INT NOT NULL,
  harga DECIMAL(10,2) NOT NULL
);

INSERT INTO penjualan (tanggal, kota, produk, jumlah, harga) VALUES
  ('2026-01-05', 'Jakarta',    'Kopi Gayo',   2, 55000.00),
  ('2026-01-05', 'Bandung',    'Teh Melati',  3, 25000.00),
  ('2026-01-06', 'Jakarta',    'Teh Melati',  1, 25000.00),
  ('2026-01-07', 'Surabaya',   'Kopi Toraja', 5, 62000.00),
  ('2026-01-08', 'Bandung',    'Gula Aren',   4, 15000.00),
  ('2026-01-09', 'Jakarta',    'Gula Aren',   2, 15000.00),
  ('2026-01-10', 'Yogyakarta', 'Kopi Gayo',   3, 55000.00),
  ('2026-01-11', 'Surabaya',   'Teh Melati',  2, 25000.00),
  ('2026-01-12', 'Yogyakarta', 'Kopi Toraja', 1, 62000.00),
  ('2026-01-13', 'Jakarta',    'Kopi Toraja', 4, 62000.00),
  ('2026-01-14', 'Bandung',    'Kopi Gayo',   1, 55000.00),
  ('2026-01-15', 'Surabaya',   'Gula Aren',   6, 15000.00);

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 12 rows affected (0.00 sec)
Records: 12  Duplicates: 0  Warnings: 0

Kedua kolom angka sengaja dibedakan tipenya: jumlah bertipe INT untuk satuan unit, sedangkan harga bertipe DECIMAL(10,2) untuk nilai uang. Perbedaan tipe ini akan terlihat pada format hasil penjumlahan — hasil SUM mengikuti tipe kolom yang dijumlahkan.

Menjumlahkan Satu Kolom #

Bentuk paling sederhana menjumlahkan seluruh nilai satu kolom:

sql
SELECT SUM(jumlah) AS total_unit
FROM penjualan;

Output yang Dihasilkan:

text
+------------+
| total_unit |
+------------+
|         34 |
+------------+
1 row in set (0.00 sec)

Hasilnya 34: seluruh nilai kolom jumlah dari dua belas baris dijumlahkan menjadi satu nilai bertipe BIGINT — SUM atas kolom integer selalu menghasilkan BIGINT agar ruang angkanya cukup lebar. Satu catatan penting: baris yang kolomnya bernilai NULL dilewati oleh SUM, sama seperti perilaku COUNT(kolom) pada chapter sebelumnya. Pada tabel kita kolom jumlah bertipe NOT NULL sehingga tidak ada nilai yang hilang, namun pada tabel nyata yang kolomnya bisa NULL, SUM hanya menjumlahkan nilai yang benar-benar ada.

Menjumlahkan Hasil Ekspresi #

Argumen SUM tidak terbatas nama kolom — kita bisa menulis ekspresi yang dihitung per baris sebelum dijumlahkan. Pola paling berguna untuk data penjualan adalah jumlah * harga, yaitu nilai transaksi setiap baris:

sql
SELECT SUM(jumlah * harga) AS total_omzet
FROM penjualan;

Output yang Dihasilkan:

text
+-------------+
| total_omzet |
+-------------+
|  1280000.00 |
+-------------+
1 row in set (0.00 sec)

Hasilnya 1280000.00 — omzet keseluruhan dari dua belas transaksi. Mari kita bedah urutan kerjanya: MySQL membaca setiap baris, menghitung jumlah * harga untuk baris tersebut (misalnya baris pertama 2 * 55000.00 = 110000.00), lalu SUM mengakumulasikan hasil per baris tersebut menjadi satu angka akhir. Hasilnya tampil dua desimal karena perkalian kolom INT dengan DECIMAL(10,2) menghasilkan DECIMAL yang mewarisi skala dua digit dari kolom harga. Perhitungan omzet semacam ini jauh lebih efisien dilakukan di server database daripada menarik seluruh baris mentah lalu menjumlahkannya ulang di kode aplikasi.

Menjumlahkan Hanya Sebagian Baris dengan WHERE #

Klausa WHERE menyaring baris sebelum agregasi berjalan, sehingga SUM hanya menerima baris yang lolos:

sql
SELECT SUM(jumlah) AS unit_jakarta,
       SUM(jumlah * harga) AS omzet_jakarta
FROM penjualan
WHERE kota = 'Jakarta';

Output yang Dihasilkan:

text
+--------------+---------------+
| unit_jakarta | omzet_jakarta |
+--------------+---------------+
|            9 |     413000.00 |
+--------------+---------------+
1 row in set (0.00 sec)

Empat transaksi Jakarta menghasilkan 9 unit dan omzet 413000.00. Urutan kerjanya penting dipahami: filter terjadi lebih dulu pada tingkat baris, penjumlahan terjadi kemudian pada himpunan baris yang tersisa. Kesalahan umum pemula adalah menempatkan kondisi per kota pada HAVING atau mengira SUM bisa “dibatasi” setelah dihitung — padahal WHERE adalah alat yang tepat untuk memilih himpunan baris mana yang ikut dijumlahkan.

Gotcha: SUM Tanpa Baris Cocok Menghasilkan NULL #

Sekarang kita uji kota yang sama sekali tidak punya transaksi. Query berikut meminta jumlah baris dan omzet untuk kota Semarang sekaligus, sehingga kedua perilaku bisa dibandingkan:

sql
SELECT COUNT(*) AS jumlah_baris,
       SUM(jumlah * harga) AS omzet
FROM penjualan
WHERE kota = 'Semarang';

Output yang Dihasilkan:

text
+--------------+-------+
| jumlah_baris | omzet |
+--------------+-------+
|            0 |  NULL |
+--------------+-------+
1 row in set (0.00 sec)

Kolom pertama berisi 0, tetapi kolom kedua berisi NULL — bukan 0. Inilah gotcha SUM yang paling sering menggigit kode aplikasi: ketika tidak ada baris yang cocok, tidak ada nilai untuk dijumlahkan, dan NULL adalah jawaban MySQL atas “tidak ada jawaban”. Bahayanya nyata karena NULL menular ke perhitungan lanjutan: NULL + 1000 tetap NULL, dan fungsi yang menerima NULL bisa gagal diam-diam. Bandingkan dengan COUNT yang selalu mengembalikan angka — kedua fungsi sengaja berbeda karena “nol baris” (informasi jumlah) memang berbeda makna dari “tidak ada nilai untuk dijumlahkan”.

Solusi standarnya adalah membungkus hasil SUM dengan fungsi COALESCE, yang mengganti NULL menjadi nilai pengganti yang kita tentukan:

sql
SELECT COALESCE(SUM(jumlah * harga), 0) AS omzet_aman
FROM penjualan
WHERE kota = 'Semarang';

Output yang Dihasilkan:

text
+------------+
| omzet_aman |
+------------+
|       0.00 |
+------------+
1 row in set (0.00 sec)

Hasilnya 0 — angka yang aman dikonsumsi aplikasi. COALESCE(x, 0) membaca argumen pertama; bila argumen pertama NULL, nilai kembalinya berubah menjadi 0. Nilai 0 pada hasil ini jujur maknanya: “tidak ada transaksi, sehingga totalnya nol”, dan interpretasi tersebut adalah keputusan bisnis kita, bukan perilaku bawaan SUM.

Menjumlahkan Nilai Unik dengan SUM(DISTINCT) #

Sama seperti COUNT, fungsi SUM juga menerima kata kunci DISTINCT untuk menyatukan nilai kembar sebelum dijumlahkan:

sql
SELECT SUM(DISTINCT harga) AS total_harga_unik
FROM penjualan;

Output yang Dihasilkan:

text
+------------------+
| total_harga_unik |
+------------------+
|        157000.00 |
+------------------+
1 row in set (0.00 sec)

Hasilnya 157000.00 — penjumlahan empat harga berbeda (55000.00 + 25000.00 + 62000.00 + 15000.00), bukan penjumlahan dua belas harga baris. Kasus pakainya memang lebih langka daripada COUNT(DISTINCT ...): misalnya menghitung total harga katalog produk yang pernah terjual, ketika kita ingin setiap produk hanya dihitung satu kali meskipun terjual berkali-kali. Nilai NULL juga diabaikan oleh varian DISTINCT ini, mengikuti aturan yang sama dengan SUM biasa.

Membersihkan Database Latihan #

Latihan selesai dan database latihan kita jatuhkan lagi agar server tetap rapi:

sql
DROP DATABASE belajar_agregasi;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

Referensi #

Total penjualan kini bisa kita hitung dengan SUM, baik dari kolom mentah maupun dari ekspresi per baris, dan kita sudah siap menghadapi NULL pada hasil kosong. Namun total saja belum tentu bermakna tanpa skala: 1280000.00 dari berapa transaksi itu rata-ratanya besar atau kecil? Chapter berikutnya membahas AVG — fungsi rata-rata yang membagi total dengan jumlah baris, lengkap dengan kebiasaannya mengabaikan NULL dan format enam desimal yang mengejutkan.