Semua fungsi yang kita bahas di section ini bekerja baris demi baris: satu nilai masuk, satu nilai keluar. Tapi pertanyaan bisnis yang sesungguhnya hampir selalu lintas baris — “berapa total penjualan bulan ini?”, “berapa rata-rata harga?”, “produk mana yang paling banyak terjual?”. Untuk itu PostgreSQL punya fungsi agregat: keluarga fungsi yang menelan banyak baris dan mengembalikan satu ringkasan. Chapter ini adalah tur perkenalan singkat — lima agregat dasar, satu perbedaan perilaku yang wajib dipahami sejak hari pertama, dan satu contoh GROUP BY sebagai pencicip. Pembahasan mendalam dari masing-masing fungsi, GROUP BY, HAVING, hingga pivot mendapat section khusus Agregasi Data ; tugas chapter ini adalah memastikan Anda mengenali wajahnya sebelum bertemu lagi.

Tabel Latihan #

Delapan baris penjualan berikut memuat tiga produk, tiga kota, dan — penting — kolom diskon yang hanya terisi pada empat baris; sisanya NULL:

sql buat-tabel.sql
-- tabel penjualan Toko Maju Jaya, Januari-Februari 2026
CREATE TABLE penjualan (
    id            integer PRIMARY KEY,
    produk        text NOT NULL,
    kota          text NOT NULL,
    jumlah        integer NOT NULL,
    harga_satuan  numeric(12,2) NOT NULL,
    diskon        numeric(5,2)
);

INSERT INTO penjualan (id, produk, kota, jumlah, harga_satuan, diskon) VALUES
    (1, 'Kopi Gayo', 'Jakarta', 10, 85000.00, 5.00),
    (2, 'Teh Melati', 'Jakarta', 25, 35000.00, NULL),
    (3, 'Gula Merah', 'Bandung', 40, 15000.00, NULL),
    (4, 'Kopi Gayo', 'Bandung', 8, 85000.00, 10.00),
    (5, 'Teh Melati', 'Surabaya', 15, 35000.00, NULL),
    (6, 'Gula Merah', 'Jakarta', 30, 15000.00, 2.50),
    (7, 'Kopi Gayo', 'Surabaya', 12, 85000.00, NULL),
    (8, 'Teh Melati', 'Bandung', 20, 35000.00, 7.50);

SELECT id, produk, kota, jumlah, harga_satuan, diskon
FROM penjualan
ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 8
 id |   produk   |   kota   | jumlah | harga_satuan | diskon 
----+------------+----------+--------+--------------+--------
  1 | Kopi Gayo  | Jakarta  |     10 |     85000.00 |   5.00
  2 | Teh Melati | Jakarta  |     25 |     35000.00 |       
  3 | Gula Merah | Bandung  |     40 |     15000.00 |       
  4 | Kopi Gayo  | Bandung  |      8 |     85000.00 |  10.00
  5 | Teh Melati | Surabaya |     15 |     35000.00 |       
  6 | Gula Merah | Jakarta  |     30 |     15000.00 |   2.50
  7 | Kopi Gayo  | Surabaya |     12 |     85000.00 |       
  8 | Teh Melati | Bandung  |     20 |     35000.00 |   7.50
(8 rows)

Kolom diskon yang setengah kosong itu bukan kelalaian — ia adalah bahan percobaan untuk membedah perilaku agregat terhadap NULL, yang menjadi pembeda paling menentukan antara hasil yang benar dan hasil yang tampak benar.

Tur Kilat Lima Agregat Dasar #

Lima fungsi berikut menutup hampir semua kebutuhan ringkasan harian:

FungsiMengembalikanContoh
count(*)Jumlah bariscount(*) → 8
sum(x)Total penjumlahansum(jumlah) → 160
avg(x)Rata-rataavg(harga_satuan) → 48750.00...
min(x)Nilai terkecilmin(harga_satuan) → 15000.00
max(x)Nilai terbesarmax(harga_satuan) → 85000.00
sql tur-cepat.sql
-- tur kilat lima agregat dasar dalam satu query
SELECT count(*) AS jumlah_baris,
       sum(jumlah) AS total_item,
       sum(jumlah * harga_satuan) AS total_penjualan,
       avg(harga_satuan) AS harga_rata_mentah,
       min(harga_satuan) AS harga_termurah,
       max(harga_satuan) AS harga_termahal
FROM penjualan;

Output yang Dihasilkan:

text
 jumlah_baris | total_item | total_penjualan | harga_rata_mentah  | harga_termurah | harga_termahal 
--------------+------------+-----------------+--------------------+----------------+----------------
            8 |        160 |      5700000.00 | 48750.000000000000 |       15000.00 |       85000.00
(1 row)

Satu baris hasil meringkas seluruh tabel: delapan transaksi, 160 item terjual, total penjualan lima juta tujuh ratus ribu rupiah, harga termurah 15.000 dan termahal 85.000. Perhatikan bahwa argumen agregat boleh berupa ekspresi — sum(jumlah * harga_satuan) menghitung nilai penjualan per baris lebih dulu lalu menjumlahkannya, pola yang akan terus muncul di laporan nyata. Rata-rata harga_satuan sengaja diberi alias harga_rata_mentah karena hasilnya membawa deretan angka nol di belakang koma — 48750.000000000000 — perilaku avg pada numeric yang akan kita rapikan dua bagian ke bawah.

count(*) vs count(kolom): Perbedaan NULL #

Inilah materi terpenting chapter ini. count(*) menghitung baris; count(kolom) menghitung nilai non-NULL pada kolom itu — dua hal yang hanya sama ketika kolom tidak pernah NULL:

-- count(*) menghitung semua baris, termasuk yang kolomnya NULL
SELECT count(*) AS semua_baris
FROM penjualan;

-- count(kolom) hanya menghitung baris dengan nilai non-NULL di kolom itu
SELECT count(diskon) AS diskon_terisi,
       count(*) - count(diskon) AS diskon_kosong
FROM penjualan;

-- count(DISTINCT kolom) menghitung nilai berbeda saja
SELECT count(DISTINCT produk) AS produk_unik,
       count(DISTINCT kota) AS kota_unik
FROM penjualan;

Output yang Dihasilkan:

text
 semua_baris 
-------------
           8
(1 row)

 diskon_terisi | diskon_kosong 
---------------+---------------
             4 |             4
(1 row)

 produk_unik | kota_unik 
-------------+-----------
           3 |         3
(1 row)

Baca hasilnya baris demi baris: count(*) melaporkan 8 — semua transaksi dihitung. count(diskon) hanya melaporkan 4 — empat baris yang kolom diskonnya terisi; selisihnya (8 - 4 = 4) bahkan bisa dipakai untuk menghitung berapa baris yang kosong, tanpa WHERE diskon IS NULL. Terakhir, count(DISTINCT produk) menyusutkan delapan baris menjadi tiga produk berbeda. Perbedaan count(*) versus count(kolom) inilah yang paling sering menjebak: count(*) menjawab “berapa transaksi”, count(diskon) menjawab “berapa transaksi yang punya diskon” — memakai yang salah untuk pertanyaan yang salah menghasilkan angka yang meyakinkan namun keliru.

avg dan Pembulatannya #

Fungsi avg membawa dua kebiasaan yang perlu dikenal sejak awal: hasil numeric-nya panjang, dan NULL-nya diabaikan, bukan dihitung nol:

-- avg() pada integer menghasilkan numeric dengan banyak angka desimal
SELECT avg(jumlah) AS rata_mentah
FROM penjualan
WHERE kota = 'Jakarta';

-- pola laporan yang lazim: bungkus dengan round()
SELECT round(avg(jumlah), 2) AS rata_dibulatkan
FROM penjualan
WHERE kota = 'Jakarta';

-- agregat mengabaikan NULL: hanya 4 baris diskon terisi yang dihitung
SELECT avg(diskon) AS rata_diskon,
       count(diskon) AS baris_yang_dihitung
FROM penjualan;

Output yang Dihasilkan:

text
     rata_mentah     
---------------------
 21.6666666666666667
(1 row)

 rata_dibulatkan 
-----------------
           21.67
(1 row)

    rata_diskon     | baris_yang_dihitung 
--------------------+---------------------
 6.2500000000000000 |                   4
(1 row)

Rata-rata penjualan Jakarta (10 + 25 + 30) / 3 menghasilkan 21.6666666666666667 — enam belas digit yang benar secara matematis tetapi tidak enak dibaca di laporan. Pola baku untuk menanganinya adalah membungkus dengan round(avg(...), 2) yang sudah kita kenal di chapter Fungsi Numerik , menghasilkan 21.67. Query terakhir membuktikan aturan pengabaian NULL: rata-rata diskon dihitung dari (5 + 10 + 2.5 + 7.5) / 4 = 6.25 — empat baris terisi saja. Kalau NULL dihitung sebagai nol, hasilnya akan 1.5625; membedakan kedua makna itu (“rata-rata diskon dari transaksi berdiskon” lawan “rata-rata diskon semua transaksi”) adalah keputusan bisnis, bukan keputusan teknis.

Sekilas GROUP BY #

Agregat menjadi benar-benar berguna begitu dibagi per kelompok — dan di situlah GROUP BY masuk. Kolom yang disebut di GROUP BY menjadi papan pembagi; agregat berjalan di dalam tiap kelompok:

sql group-by.sql
-- satu kolom: ringkasan penjualan per produk
SELECT produk,
       count(*) AS transaksi,
       sum(jumlah) AS total_item
FROM penjualan
GROUP BY produk
ORDER BY produk;

-- dua kolom: ringkasan per produk per kota
SELECT produk, kota,
       count(*) AS transaksi,
       sum(jumlah * harga_satuan) AS total_nilai
FROM penjualan
GROUP BY produk, kota
ORDER BY produk, kota;

Output yang Dihasilkan:

text
   produk   | transaksi | total_item 
------------+-----------+------------
 Gula Merah |         2 |         70
 Kopi Gayo  |         3 |         30
 Teh Melati |         3 |         60
(3 rows)

   produk   |   kota   | transaksi | total_nilai 
------------+----------+-----------+-------------
 Gula Merah | Bandung  |         1 |   600000.00
 Gula Merah | Jakarta  |         1 |   450000.00
 Kopi Gayo  | Bandung  |         1 |   680000.00
 Kopi Gayo  | Jakarta  |         1 |   850000.00
 Kopi Gayo  | Surabaya |         1 |  1020000.00
 Teh Melati | Bandung  |         1 |   700000.00
 Teh Melati | Jakarta  |         1 |   875000.00
 Teh Melati | Surabaya |         1 |   525000.00
(8 rows)

Query pertama menyusutkan delapan baris menjadi tiga — satu per produk, lengkap dengan jumlah transaksi dan total item; Gula Merah menyalip Kopi Gayo di total item (70 lawan 30) meski nilainya jauh lebih kecil. Query kedua membagi lebih halus lagi dengan GROUP BY produk, kota: setiap pasangan produk-kota menjadi kelompok sendiri dengan nilai penjualan totalnya, seperti Kopi Gayo di Surabaya senilai 1020000.00. Aturan main GROUP BY — kolom apa saja yang boleh muncul di SELECT, menyaring hasil kelompok dengan HAVING, agregat bersarang dengan CASE — adalah materi inti chapter GROUP BY di section berikutnya; chapter ini hanya memperkenalkan selera pertamanya.

Dengan lima agregat dasar di tangan, peta fungsi section ini nyaris lengkap. Tersisa satu bahasa yang belum dibedah secara sistematis: operator — simbol-simbol yang menghubungkan nilai menjadi ekspresi, beserta urutan prioritasnya yang menentukan hasil tanpa bertanya. Chapter berikutnya merapikan semuanya: Operator dan Ekspresi .

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

avg-pembulatan.sql

Unduh berkas
sql
-- avg() pada integer menghasilkan numeric dengan banyak angka desimal
SELECT avg(jumlah) AS rata_mentah
FROM penjualan
WHERE kota = 'Jakarta';

-- pola laporan yang lazim: bungkus dengan round()
SELECT round(avg(jumlah), 2) AS rata_dibulatkan
FROM penjualan
WHERE kota = 'Jakarta';

-- agregat mengabaikan NULL: hanya 4 baris diskon terisi yang dihitung
SELECT avg(diskon) AS rata_diskon,
       count(diskon) AS baris_yang_dihitung
FROM penjualan;

buat-tabel.sql

Unduh berkas
sql
-- tabel penjualan Toko Maju Jaya, Januari-Februari 2026
CREATE TABLE penjualan (
    id            integer PRIMARY KEY,
    produk        text NOT NULL,
    kota          text NOT NULL,
    jumlah        integer NOT NULL,
    harga_satuan  numeric(12,2) NOT NULL,
    diskon        numeric(5,2)
);

INSERT INTO penjualan (id, produk, kota, jumlah, harga_satuan, diskon) VALUES
    (1, 'Kopi Gayo', 'Jakarta', 10, 85000.00, 5.00),
    (2, 'Teh Melati', 'Jakarta', 25, 35000.00, NULL),
    (3, 'Gula Merah', 'Bandung', 40, 15000.00, NULL),
    (4, 'Kopi Gayo', 'Bandung', 8, 85000.00, 10.00),
    (5, 'Teh Melati', 'Surabaya', 15, 35000.00, NULL),
    (6, 'Gula Merah', 'Jakarta', 30, 15000.00, 2.50),
    (7, 'Kopi Gayo', 'Surabaya', 12, 85000.00, NULL),
    (8, 'Teh Melati', 'Bandung', 20, 35000.00, 7.50);

SELECT id, produk, kota, jumlah, harga_satuan, diskon
FROM penjualan
ORDER BY id;

count-vs-kolom.sql

Unduh berkas
sql
-- count(*) menghitung semua baris, termasuk yang kolomnya NULL
SELECT count(*) AS semua_baris
FROM penjualan;

-- count(kolom) hanya menghitung baris dengan nilai non-NULL di kolom itu
SELECT count(diskon) AS diskon_terisi,
       count(*) - count(diskon) AS diskon_kosong
FROM penjualan;

-- count(DISTINCT kolom) menghitung nilai berbeda saja
SELECT count(DISTINCT produk) AS produk_unik,
       count(DISTINCT kota) AS kota_unik
FROM penjualan;

group-by.sql

Unduh berkas
sql
-- satu kolom: ringkasan penjualan per produk
SELECT produk,
       count(*) AS transaksi,
       sum(jumlah) AS total_item
FROM penjualan
GROUP BY produk
ORDER BY produk;

-- dua kolom: ringkasan per produk per kota
SELECT produk, kota,
       count(*) AS transaksi,
       sum(jumlah * harga_satuan) AS total_nilai
FROM penjualan
GROUP BY produk, kota
ORDER BY produk, kota;

tur-cepat.sql

Unduh berkas
sql
-- tur kilat lima agregat dasar dalam satu query
SELECT count(*) AS jumlah_baris,
       sum(jumlah) AS total_item,
       sum(jumlah * harga_satuan) AS total_penjualan,
       avg(harga_satuan) AS harga_rata_mentah,
       min(harga_satuan) AS harga_termurah,
       max(harga_satuan) AS harga_termahal
FROM penjualan;