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:

sql buat-tabel.sql
-- 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:

text
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:

sql sum-dasar.sql
-- 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:

text
 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:

  1. sum(jumlah * harga) — PostgreSQL mengevaluasi jumlah * harga pada setiap baris terlebih dahulu: baris 1 menjadi 10 × 85000 = 850000, baris 2 menjadi 25 × 35000 = 875000, dan seterusnya untuk 12 baris.
  2. Pengjumlahan — kedua belas nilai antara itu kemudian dijumlahkan menjadi 9180000.00, nilai total penjualan dua bulan itu.
  3. Alias AS total_nilai_penjualan — nama kolom hasil dibuat deskriptif untuk laporan; tanpa alias psql akan menampilkan label panjang sum.

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 :

sql sum-null.sql
-- 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:

text
 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:

text
 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:

text
 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
sql
-- 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
sql
-- 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
sql
-- 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
sql
-- 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
sql
-- 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;