Setelah tahu “berapa banyak” lewat count, pertanyaan bisnis berikutnya hampir selalu “berapa nilainya”: total penjualan bulan ini, total unit yang terkirim, total diskon yang diberikan. Fungsi sum menjawab semuanya dengan satu pola — menjumlahkan seluruh nilai sebuah ekspresi menjadi satu angka. Chapter ini membedah tiga watak sum yang wajib dikuasai sebelum dipakai di laporan sungguhan: argumennya boleh ekspresi (bukan sekadar nama kolom), NULL diabaikan begitu saja, dan himpunan kosong menghasilkan NULL — bukan nol. Ketiganya kami uji pada tabel penjualan yang sama dengan chapter COUNT
.
Tabel Latihan: penjualan #
Kita pakai tabel yang sama untuk seluruh section ini — 12 transaksi Toko Maju Jaya sepanjang Januari–Februari 2026. Kolom diskon hanya terisi pada 6 baris dan kategori kosong pada 2 baris; kedua kolom itu menjadi bahan percobaan perilaku NULL:
-- Tabel latihan utama Section Agregasi Data:
-- transaksi penjualan Toko Maju Jaya, Januari-Februari 2026.
CREATE TABLE penjualan (
id integer PRIMARY KEY,
tanggal date NOT NULL,
penjual text NOT NULL,
kategori text, -- NULL = transaksi belum dikategorikan
produk text NOT NULL,
jumlah integer NOT NULL, -- unit terjual
harga numeric(10,2) NOT NULL, -- harga satuan, rupiah
diskon numeric(5,2) -- NULL = tanpa diskon
);
INSERT INTO penjualan (id, tanggal, penjual, kategori, produk, jumlah, harga, diskon) VALUES
( 1, DATE '2026-01-05', 'Budi', 'Kopi', 'Kopi Gayo', 10, 85000.00, 5.00),
( 2, DATE '2026-01-08', 'Siti', 'Teh', 'Teh Melati', 25, 35000.00, NULL),
( 3, DATE '2026-01-12', 'Andi', 'Gula', 'Gula Merah', 40, 15000.00, NULL),
( 4, DATE '2026-01-15', 'Budi', 'Kopi', 'Kopi Toraja', 8, 90000.00, 10.00),
( 5, DATE '2026-01-20', 'Dewi', NULL, 'Kopi Gayo', 12, 85000.00, NULL),
( 6, DATE '2026-01-22', 'Siti', 'Teh', 'Teh Hijau', 15, 40000.00, 7.50),
( 7, DATE '2026-01-25', 'Andi', 'Gula', 'Gula Merah', 30, 15000.00, 2.50),
( 8, DATE '2026-02-02', 'Budi', NULL, 'Teh Melati', 20, 35000.00, NULL),
( 9, DATE '2026-02-05', 'Dewi', 'Kopi', 'Kopi Gayo', 18, 85000.00, 8.00),
(10, DATE '2026-02-10', 'Siti', 'Teh', 'Teh Melati', 22, 35000.00, NULL),
(11, DATE '2026-02-14', 'Andi', 'Gula', 'Gula Merah', 35, 15000.00, NULL),
(12, DATE '2026-02-18', 'Budi', 'Kopi', 'Kopi Toraja', 6, 90000.00, 12.00);
SELECT id, tanggal, penjual, kategori, produk, jumlah, harga, diskon
FROM penjualan
ORDER BY id;Output yang Dihasilkan:
CREATE TABLE
INSERT 0 12
id | tanggal | penjual | kategori | produk | jumlah | harga | diskon
----+------------+---------+----------+-------------+--------+----------+--------
1 | 2026-01-05 | Budi | Kopi | Kopi Gayo | 10 | 85000.00 | 5.00
2 | 2026-01-08 | Siti | Teh | Teh Melati | 25 | 35000.00 |
3 | 2026-01-12 | Andi | Gula | Gula Merah | 40 | 15000.00 |
4 | 2026-01-15 | Budi | Kopi | Kopi Toraja | 8 | 90000.00 | 10.00
5 | 2026-01-20 | Dewi | | Kopi Gayo | 12 | 85000.00 |
6 | 2026-01-22 | Siti | Teh | Teh Hijau | 15 | 40000.00 | 7.50
7 | 2026-01-25 | Andi | Gula | Gula Merah | 30 | 15000.00 | 2.50
8 | 2026-02-02 | Budi | | Teh Melati | 20 | 35000.00 |
9 | 2026-02-05 | Dewi | Kopi | Kopi Gayo | 18 | 85000.00 | 8.00
10 | 2026-02-10 | Siti | Teh | Teh Melati | 22 | 35000.00 |
11 | 2026-02-14 | Andi | Gula | Gula Merah | 35 | 15000.00 |
12 | 2026-02-18 | Budi | Kopi | Kopi Toraja | 6 | 90000.00 | 12.00
(12 rows)Menjumlahkan Kolom dan Ekspresi #
Bentuk paling sederhana sum menerima satu nama kolom dan menjumlahkan seluruh nilainya. Kekuatan sesungguhnya muncul saat argumennya berupa ekspresi — nilai per baris dihitung lebih dulu, baru dijumlahkan:
-- SUM menjumlahkan seluruh nilai kolom menjadi satu angka.
SELECT sum(jumlah) AS total_item
FROM penjualan;
-- Argumen SUM boleh berupa ekspresi: nilai tiap baris dihitung dulu,
-- baru dijumlahkan. Ini pola standar untuk total rupiah.
SELECT sum(jumlah * harga) AS total_nilai_penjualan
FROM penjualan;
-- Keduanya sekaligus: berapa unit terjual dan berapa nilai rupiahnya.
SELECT sum(jumlah) AS total_item,
sum(jumlah * harga) AS total_nilai,
count(*) AS transaksi
FROM penjualan;Output yang Dihasilkan:
total_item
------------
241
(1 row)
total_nilai_penjualan
-----------------------
9180000.00
(1 row)
total_item | total_nilai | transaksi
------------+-------------+-----------
241 | 9180000.00 | 12
(1 row)Mari kita bedah query kedua baris per barisnya:
sum(jumlah * harga)— PostgreSQL mengevaluasijumlah * hargapada setiap baris terlebih dahulu: baris 1 menjadi10 × 85000 = 850000, baris 2 menjadi25 × 35000 = 875000, dan seterusnya untuk 12 baris.- Pengjumlahan — kedua belas nilai antara itu kemudian dijumlahkan menjadi
9180000.00, nilai total penjualan dua bulan itu. - Alias
AS total_nilai_penjualan— nama kolom hasil dibuat deskriptif untuk laporan; tanpa alias psql akan menampilkan label panjangsum.
Perhatikan pula bahwa sum(jumlah) menghasilkan 241 — total unit terjual, bukan total baris (12). Query ketiga menggabungkan keduanya dengan count(*) sehingga satu baris hasil langsung menjawab tiga pertanyaan: 12 transaksi, 241 unit, 9,18 juta rupiah.
SUM Mengabaikan NULL #
sum mewarisi aturan agregat pada umumnya: NULL dilewati, bukan dianggap nol. Ini berbeda dari count(*) yang menghitung baris apa adanya — perbedaan yang sudah kita petakan di chapter COUNT
:
-- SUM mengabaikan NULL: hanya baris dengan diskon terisi yang ikut dijumlahkan.
SELECT count(*) AS semua_baris,
count(diskon) AS diskon_terisi,
sum(diskon) AS total_diskon
FROM penjualan;
-- Pola ini aman untuk kolom yang jarang terisi (sparse):
-- total tetap benar tanpa perlu WHERE diskon IS NOT NULL.
SELECT sum(jumlah) AS total_item_semua,
sum(jumlah * harga) AS total_nilai_semua
FROM penjualan
WHERE kategori = 'Teh';
-- NULL di kolom lain tidak mengganggu SUM pada kolom rapi:
-- baris dengan kategori NULL tetap dihitung untuk sum(harga).
SELECT count(*) AS semua_baris,
count(kategori) AS kategori_terisi,
sum(harga) AS total_harga_satuan
FROM penjualan;Output yang Dihasilkan:
semua_baris | diskon_terisi | total_diskon
-------------+---------------+--------------
12 | 6 | 45.00
(1 row)
total_item_semua | total_nilai_semua
------------------+-------------------
62 | 2245000.00
(1 row)
semua_baris | kategori_terisi | total_harga_satuan
-------------+-----------------+--------------------
12 | 10 | 625000.00
(1 row)Query pertama memperlihatkan mekanismenya: dari 12 baris, hanya 6 baris berdiskon yang diikutkan, sehingga sum(diskon) menghasilkan 45.00 — penjumlahan dari 5 + 10 + 7.5 + 2.5 + 8 + 12. Kalau NULL dianggap nol, hasilnya kebetulan sama; justru pada avg perbedaan ini meledak, dan kita buktikan di chapter AVG
. Query kedua menunjukkan bahwa filter WHERE bekerja seperti biasa — sum hanya menjumlahkan baris yang lolos. Query ketiga menegaskan bahwa NULL bersifat per-kolom: baris dengan kategori NULL tetap menyumbang nilainya pada sum(harga).
Himpunan Kosong Menghasilkan NULL #
Saat tidak ada satu baris pun yang dijumlahkan, sum tidak menghasilkan 0 melainkan NULL. Perilaku ini masuk akal secara matematis — “jumlah dari tidak ada apa-apa” memang tidak terdefinisi — tetapi sering mengejutkan aplikasi yang mengharapkan angka nol:
-- Filter yang tidak cocok dengan satu baris pun menghasilkan himpunan kosong.
-- SUM pada himpunan kosong mengembalikan NULL, BUKAN 0.
SELECT sum(jumlah) AS total_item_maret
FROM penjualan
WHERE tanggal >= DATE '2026-03-01';
-- Bandingkan dua agregat pada filter yang sama:
-- count() mengembalikan 0, sum() mengembalikan NULL.
SELECT count(*) AS transaksi,
sum(jumlah) AS total_item,
sum(diskon) AS total_diskon
FROM penjualan
WHERE tanggal >= DATE '2026-03-01';
-- Pola baku untuk laporan: bungkus dengan coalesce() agar NULL menjadi 0.
SELECT coalesce(sum(jumlah), 0) AS total_item,
coalesce(sum(jumlah * harga), 0) AS total_nilai
FROM penjualan
WHERE tanggal >= DATE '2026-03-01';Output yang Dihasilkan:
total_item_maret
------------------
(1 row)
transaksi | total_item | total_diskon
-----------+------------+--------------
0 | |
(1 row)
total_item | total_nilai
------------+-------------
0 | 0
(1 row)Query kedua menempatkan dua watak bersebelahan: count(*) tetap kembali dengan 0, sementara kedua kolom sum kosong NULL. Query ketiga adalah pola baku penanganannya — coalesce(nilai, 0) mengganti NULL dengan nol, sehingga laporan bulan sepi tetap menampilkan 0 alih-alih sel kosong yang bisa dibaca keliru.
Tipe Hasil dan Ruang Angka #
Berapa pun jumlahnya, penjumlahan harus aman dari overflow. PostgreSQL menangani ini dengan menaikkan tipe hasil sum lebih tinggi daripada tipe kolomnya — fungsi pg_typeof() memperlihatkan tipe hasil tersebut tanpa menduga-duga:
-- PostgreSQL memperlebar tipe hasil SUM agar penjumlahan tidak meluap (overflow).
-- sum() pada kolom integer menghasilkan bigint, bukan integer.
SELECT pg_typeof(sum(jumlah)) AS tipe_hasil
FROM penjualan;
-- sum() pada kolom numeric menghasilkan numeric (ketelitian tak terbatas).
SELECT pg_typeof(sum(harga)) AS tipe_hasil
FROM penjualan;
-- sum() pada kolom bigint pun dinaikkan ke numeric — menjaga angka besar
-- seperti total saldo tetap aman di luar jangkauan bigint.
SELECT pg_typeof(sum(id::bigint)) AS tipe_hasil
FROM penjualan;Output yang Dihasilkan:
tipe_hasil
------------
bigint
(1 row)
tipe_hasil
------------
numeric
(1 row)
tipe_hasil
------------
numeric
(1 row)Tiga hasilnya: bigint, numeric, numeric. Penjumlahan kolom integer dikembalikan sebagai bigint (jangkauan hingga sekitar 9,2 × 10¹⁸) sehingga menjumlah jutaan baris nilai miliaran pun tidak meluap; kolom bigint bahkan dinaikkan lagi ke numeric yang ketelitiannya tidak terbatas. Anda tidak perlu melakukan apa pun untuk mendapat perlindungan ini — cukup sadar bahwa hasil sum bisa lebih lebar dari kolomnya, misalnya saat hasilnya akan disimpan kembali ke kolom integer.
Total penjualan sudah di tangan; pertanyaan berikutnya biasanya berbunyi “rata-ratanya berapa?”. Sayangnya rata-rata membawa kebiasaan sendiri: desimal yang panjang, tipe yang berubah, dan jebakan memilih pembagi yang tepat. Chapter berikutnya membedah semuanya: AVG .
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
buat-tabel.sql
Unduh berkas-- Tabel latihan utama Section Agregasi Data:
-- transaksi penjualan Toko Maju Jaya, Januari-Februari 2026.
CREATE TABLE penjualan (
id integer PRIMARY KEY,
tanggal date NOT NULL,
penjual text NOT NULL,
kategori text, -- NULL = transaksi belum dikategorikan
produk text NOT NULL,
jumlah integer NOT NULL, -- unit terjual
harga numeric(10,2) NOT NULL, -- harga satuan, rupiah
diskon numeric(5,2) -- NULL = tanpa diskon
);
INSERT INTO penjualan (id, tanggal, penjual, kategori, produk, jumlah, harga, diskon) VALUES
( 1, DATE '2026-01-05', 'Budi', 'Kopi', 'Kopi Gayo', 10, 85000.00, 5.00),
( 2, DATE '2026-01-08', 'Siti', 'Teh', 'Teh Melati', 25, 35000.00, NULL),
( 3, DATE '2026-01-12', 'Andi', 'Gula', 'Gula Merah', 40, 15000.00, NULL),
( 4, DATE '2026-01-15', 'Budi', 'Kopi', 'Kopi Toraja', 8, 90000.00, 10.00),
( 5, DATE '2026-01-20', 'Dewi', NULL, 'Kopi Gayo', 12, 85000.00, NULL),
( 6, DATE '2026-01-22', 'Siti', 'Teh', 'Teh Hijau', 15, 40000.00, 7.50),
( 7, DATE '2026-01-25', 'Andi', 'Gula', 'Gula Merah', 30, 15000.00, 2.50),
( 8, DATE '2026-02-02', 'Budi', NULL, 'Teh Melati', 20, 35000.00, NULL),
( 9, DATE '2026-02-05', 'Dewi', 'Kopi', 'Kopi Gayo', 18, 85000.00, 8.00),
(10, DATE '2026-02-10', 'Siti', 'Teh', 'Teh Melati', 22, 35000.00, NULL),
(11, DATE '2026-02-14', 'Andi', 'Gula', 'Gula Merah', 35, 15000.00, NULL),
(12, DATE '2026-02-18', 'Budi', 'Kopi', 'Kopi Toraja', 6, 90000.00, 12.00);
SELECT id, tanggal, penjual, kategori, produk, jumlah, harga, diskon
FROM penjualan
ORDER BY id;
sum-dasar.sql
Unduh berkas-- SUM menjumlahkan seluruh nilai kolom menjadi satu angka.
SELECT sum(jumlah) AS total_item
FROM penjualan;
-- Argumen SUM boleh berupa ekspresi: nilai tiap baris dihitung dulu,
-- baru dijumlahkan. Ini pola standar untuk total rupiah.
SELECT sum(jumlah * harga) AS total_nilai_penjualan
FROM penjualan;
-- Keduanya sekaligus: berapa unit terjual dan berapa nilai rupiahnya.
SELECT sum(jumlah) AS total_item,
sum(jumlah * harga) AS total_nilai,
count(*) AS transaksi
FROM penjualan;
sum-himpunan-kosong.sql
Unduh berkas-- Filter yang tidak cocok dengan satu baris pun menghasilkan himpunan kosong.
-- SUM pada himpunan kosong mengembalikan NULL, BUKAN 0.
SELECT sum(jumlah) AS total_item_maret
FROM penjualan
WHERE tanggal >= DATE '2026-03-01';
-- Bandingkan dua agregat pada filter yang sama:
-- count() mengembalikan 0, sum() mengembalikan NULL.
SELECT count(*) AS transaksi,
sum(jumlah) AS total_item,
sum(diskon) AS total_diskon
FROM penjualan
WHERE tanggal >= DATE '2026-03-01';
-- Pola baku untuk laporan: bungkus dengan coalesce() agar NULL menjadi 0.
SELECT coalesce(sum(jumlah), 0) AS total_item,
coalesce(sum(jumlah * harga), 0) AS total_nilai
FROM penjualan
WHERE tanggal >= DATE '2026-03-01';
sum-null.sql
Unduh berkas-- SUM mengabaikan NULL: hanya baris dengan diskon terisi yang ikut dijumlahkan.
SELECT count(*) AS semua_baris,
count(diskon) AS diskon_terisi,
sum(diskon) AS total_diskon
FROM penjualan;
-- Pola ini aman untuk kolom yang jarang terisi (sparse):
-- total tetap benar tanpa perlu WHERE diskon IS NOT NULL.
SELECT sum(jumlah) AS total_item_semua,
sum(jumlah * harga) AS total_nilai_semua
FROM penjualan
WHERE kategori = 'Teh';
-- NULL di kolom lain tidak mengganggu SUM pada kolom rapi:
-- baris dengan kategori NULL tetap dihitung untuk sum(harga).
SELECT count(*) AS semua_baris,
count(kategori) AS kategori_terisi,
sum(harga) AS total_harga_satuan
FROM penjualan;
sum-tipe-hasil.sql
Unduh berkas-- PostgreSQL memperlebar tipe hasil SUM agar penjumlahan tidak meluap (overflow).
-- sum() pada kolom integer menghasilkan bigint, bukan integer.
SELECT pg_typeof(sum(jumlah)) AS tipe_hasil
FROM penjualan;
-- sum() pada kolom numeric menghasilkan numeric (ketelitian tak terbatas).
SELECT pg_typeof(sum(harga)) AS tipe_hasil
FROM penjualan;
-- sum() pada kolom bigint pun dinaikkan ke numeric — menjaga angka besar
-- seperti total saldo tetap aman di luar jangkauan bigint.
SELECT pg_typeof(sum(id::bigint)) AS tipe_hasil
FROM penjualan;