Fungsi agregasi

Merangkum banyak baris menjadi satu nilai dengan COUNT, SUM, AVG, MIN, dan MAX.

Daftar isi

Topik ini membuka modul agregasi dengan lima fungsi yang paling sering dipakai, yaitu COUNT, SUM, AVG, MIN, dan MAX. Fungsi agregasi merangkum banyak baris menjadi satu nilai, dan perbedaan perilakunya terhadap baris kosong menjadi inti pembahasan karena selisih itu melahirkan angka yang menyesatkan bila tidak dipahami.

Blok penyiapan dataset#

bash
# siapkan ulang tabel dan data contoh untuk topik ini
PGOPTIONS='-c client_min_messages=warning' psql -q -U postgres -d perpustakaan <<'SQL'
DROP TABLE IF EXISTS peminjaman, buku, anggota, kategori CASCADE;
CREATE TABLE kategori (id SERIAL PRIMARY KEY, nama TEXT NOT NULL UNIQUE);
CREATE TABLE anggota (id SERIAL PRIMARY KEY, nama TEXT NOT NULL, email TEXT UNIQUE, bergabung DATE NOT NULL, aktif BOOLEAN NOT NULL DEFAULT TRUE);
CREATE TABLE buku (id SERIAL PRIMARY KEY, judul TEXT NOT NULL, penulis TEXT NOT NULL, tahun INTEGER NOT NULL, kategori_id INTEGER REFERENCES kategori(id));
CREATE TABLE peminjaman (id SERIAL PRIMARY KEY, anggota_id INTEGER NOT NULL REFERENCES anggota(id), buku_id INTEGER NOT NULL REFERENCES buku(id), tanggal_pinjam DATE NOT NULL, tanggal_kembali DATE);
INSERT INTO kategori (nama) VALUES ('Pemrograman'), ('Sains'), ('Sejarah'), ('Fiksi'), ('Statistika');
INSERT INTO anggota (nama, email, bergabung, aktif) VALUES ('Ali Rahman', '[email protected]', '2024-01-15', TRUE), ('Budi Santoso', '[email protected]', '2024-03-02', TRUE), ('Citra Kirana', '[email protected]', '2024-05-20', FALSE), ('Dewi Lestari', '[email protected]', '2024-08-11', TRUE), ('Eka Pratama', NULL, '2025-01-30', TRUE);
INSERT INTO buku (judul, penulis, tahun, kategori_id) VALUES ('Dasar Basis Data', 'Sari Wulandari', 2021, 1), ('Jaringan Komputer', 'Ahmad Fauzi', 2019, 1), ('Fisika untuk Pemula', 'Rina Marlina', 2020, 2), ('Kimia Organik', 'Rina Marlina', 2022, 2), ('Sejarah Nusantara', 'Bambang Wijaya', 2018, 3), ('Cerita Kota Lama', 'Nur Aisyah', 2023, 4), ('Statistika Terapan', 'Sari Wulandari', 2022, 5), ('Algoritma dan Struktur Data', 'Ahmad Fauzi', 2021, 1);
INSERT INTO peminjaman (anggota_id, buku_id, tanggal_pinjam, tanggal_kembali) VALUES (1, 1, '2025-03-01', '2025-03-15'), (1, 7, '2025-03-05', '2025-03-20'), (2, 3, '2025-03-10', NULL), (3, 5, '2025-02-20', '2025-03-01'), (2, 1, '2025-04-02', NULL), (4, 6, '2025-04-07', NULL), (5, 8, '2025-04-09', '2025-04-25'), (4, 2, '2025-05-01', NULL);
SQL

COUNT dan perbedaan dua bentuknya#

COUNT(*) menghitung seluruh baris, sedangkan COUNT(kolom) hanya menghitung baris yang kolomnya terisi. Dataset ini memuat lima anggota dengan satu email kosong, sehingga keduanya berbeda sekaligus menjadi demonstrasi paling jelas.

bash
# seluruh baris versus baris dengan email terisi
psql -U postgres -d perpustakaan -c "SELECT COUNT(*) AS semua, COUNT(email) AS beremail, COUNT(DISTINCT penulis) AS penulis_unik FROM anggota a, buku b;"
text Keluaran
 semua | beremail | penulis_unik
-------+----------+--------------
    40 |       32 |            5
(1 row)

Keluaran ketiga kolom ini memakai dataset lintas tabel secara sengaja untuk menunjukkan bentuk COUNT DISTINCT, yaitu penghitungan nilai unik setelah duplikasi dibuang. Pembacaan yang benar memisahkan tiga pertanyaan, yaitu berapa barisnya, berapa yang kolomnya terisi, dan berapa nilai uniknya.

SUM, AVG, dan baris kosong#

SUM menjumlahkan nilai kolom yang terisi dan mengabaikan baris kosongnya, sedangkan AVG membagi jumlah itu dengan banyaknya nilai terisi, bukan dengan banyaknya baris. Pada kolom yang seluruhnya kosong, SUM menghasilkan NULL alih-alih nol, karena menjumlahkan apa yang tidak ada tidak menghasilkan nol yang bermakna.

bash
# agregasi tanggal kembali yang sebagian kosong
psql -U postgres -d perpustakaan -c "SELECT COUNT(tanggal_kembali) AS sudah_kembali, MAX(tanggal_kembali) AS terakhir_kembali, MIN(tanggal_pinjam) AS pertama_pinjam FROM peminjaman;"
text Keluaran
 sudah_kembali | terakhir_kembali | pertama_pinjam
---------------+------------------+----------------
             4 | 2025-04-25       | 2025-02-20
(1 row)

Empat dari delapan peminjaman sudah kembali, sehingga COUNT-nya empat, sedangkan MAX dan MIN membaca batas rentang tanggal dari kolom yang sama. Fungsi MIN dan MAX bekerja pada tipe apa pun yang dapat diperbandingkan, termasuk tanggal dan teks.

Pembulatan AVG dan konversi eksplisit#

AVG atas kolom integer menghasilkan pecahan karena hasilnya numeric secara otomatis, dan tampilan panjangnya memerlukan pembulatan melalui round() dengan jumlah tempat desimal yang dinyatakan. Pola ini berlaku pada setiap rata-rata yang tampil kepada manusia.

bash
# rata-rata tahun terbit dengan pembulatan
psql -U postgres -d perpustakaan -c "SELECT ROUND(AVG(tahun), 1) AS rata_tahun, MIN(tahun) AS tertua, MAX(tahun) AS termuda FROM buku;"
text Keluaran
 rata_tahun | tertua | termuda
------------+--------+---------
     2020.8 |   2018 |    2023
(1 row)

Nilai tengah rata-rata selalu disertai ukuran penyebarannya pada laporan serius, misalnya MIN dan MAX yang ditampilkan berdampingan, karena rata-rata tunggal menyembunyikan sebaran yang lebar.

FILTER untuk agregasi bersyarat#

Klausa FILTER (WHERE …) milik PostgreSQL menerapkan satu agregasi hanya pada baris yang lolos syaratnya, sehingga beberapa hitungan bersyarat diletakkan pada satu baris hasil tanpa subquery. Bentuk ini lebih ringkas daripada CASE di dalam fungsi agregasi dan lebih mudah dibaca.

bash
# hitung peminjaman aktif dan selesai dalam satu baris
psql -U postgres -d perpustakaan -c "SELECT COUNT(*) FILTER (WHERE tanggal_kembali IS NULL) AS masih_dipinjam, COUNT(*) FILTER (WHERE tanggal_kembali IS NOT NULL) AS sudah_kembali FROM peminjaman;"
text Keluaran
 masih_dipinjam | sudah_kembali
----------------+---------------
              4 |             4
(1 row)

Dua hitungan yang biasanya menjadi dua query kini berdampingan, dan pola ini meluas ke SUM serta AVG dengan syarat yang berbeda-beda pada kolom yang sama.

Kesalahan umum#

Kesalahan pertama adalah memperlakukan COUNT(kolom) sama dengan COUNT(*), sehingga baris kosong diam-diam tidak terhitung dan angka laporan menyusut tanpa penjelasan. Kesalahan kedua adalah mengira SUM kolom kosong bernilai nol, padahal hasilnya NULL, dan pembungkusnya dengan COALESCE dilakukan secara sadar pada laporan yang menuntut angka.

Kesalahan ketiga adalah membagi hasil agregasi dengan COUNT(*) saat seharusnya dibagi COUNT(kolom), sehingga rata-rata terpangkas oleh baris yang tidak dihitung penyebutnya. Kesalahan keempat adalah memakai AVG untuk nilai uang lalu membulatkannya sendiri, padahal pembulatan eksplisit dengan round() pada numeric adalah jalan yang dapat diaudit.

Ringkasan#

  • COUNT(*) menghitung baris, COUNT(kolom) menghitung nilai terisi, dan COUNT DISTINCT menghitung nilai unik.
  • SUM dan AVG mengabaikan NULL, dengan AVG membagi atas banyaknya nilai terisi, dan SUM kolom kosong menghasilkan NULL.
  • MIN dan MAX bekerja pada tipe yang dapat diperbandingkan, termasuk tanggal dan teks.
  • FILTER (WHERE …) memberi syarat khusus pada satu agregasi, sehingga beberapa hitungan bersyarat berdampingan dalam satu baris.

Referensi#