Rata-rata adalah angka yang paling sering diminta dan paling sering disalahartikan. “Berapa rata-rata belanja pelanggan kita?” terdengar sederhana, tetapi jawabannya bergantung pada dua keputusan tersembunyi: rata-rata dari nilai apa, dan dibagi berapa. Fungsi avg menghitung total nilai lalu membaginya dengan banyaknya nilai — dan di situlah letak kejutannya: avg pada kolom integer menghasilkan numeric penuh desimal, NULL diabaikan dari pembagi, dan rata-rata sederhana sering kali menjawab pertanyaan yang salah ketika setiap nilai punya bobot berbeda. Chapter ini membedah satu per satu memakai tabel penjualan yang sudah menemani kita sejak chapter COUNT .

Tabel Latihan: penjualan #

Tabel yang sama untuk seluruh section: 12 transaksi Toko Maju Jaya, Januari–Februari 2026. Dua kolom sengaja memuat NULL — diskon kosong pada 6 baris, kategori kosong pada 2 baris — karena pembagi rata-rata adalah tempat NULL paling sering menjebak:

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)

Rata-rata pada Kolom Integer #

Kita mulai dari kolom jumlah yang bertipe integer. Total 241 unit tersebar di 12 transaksi, jadi rata-ratanya 241 ÷ 12 — angka yang tidak bulat, dan inilah kebiasaan pertama avg:

sql avg-dasar.sql
-- AVG = total nilai dibagi banyaknya nilai: 241 unit dibagi 12 transaksi.
SELECT avg(jumlah) AS rata_mentah
FROM penjualan;

-- Tipe hasilnya numeric — bukan integer — meski kolomnya integer.
SELECT pg_typeof(avg(jumlah)) AS tipe_hasil
FROM penjualan;

-- Dua cara merapikan hasilnya:
-- round() membulatkan nilainya (tetap numeric),
-- to_char() hanya memformat tampilan (hasilnya text).
SELECT round(avg(jumlah), 2)                AS rata_dibulatkan,
       to_char(avg(jumlah), 'FM9999990.00') AS rata_tampilan
FROM penjualan;

Output yang Dihasilkan:

text
     rata_mentah     
---------------------
 20.0833333333333333
(1 row)

 tipe_hasil 
------------
 numeric
(1 row)

 rata_dibulatkan | rata_tampilan 
-----------------+---------------
           20.08 | 20.08
(1 row)

avg(jumlah) menghasilkan 20.0833333333333333 — enam belas digit yang benar secara matematis. Ini bukan kebetulan desimal: pg_typeof membuktikan tipe hasilnya numeric, padahal kolomnya integer. PostgreSQL sengaja menghindari pembagian bilangan bulat yang akan memotong hasil menjadi 20; avg selalu mengembalikan tipe pecahan supaya presisi tidak hilang diam-diam. Untuk tampilan laporan, ada dua alat yang sering tertukar:

  1. round(avg(jumlah), 2) membulatkan nilainya menjadi 20.08 — hasilnya tetap bertipe numeric dan masih bisa dihitung lanjutan.
  2. to_char(avg(jumlah), 'FM9999990.00') hanya memformat tampilan menjadi teks 20.08 — nilai aslinya tidak tersentuh, dan hasilnya sudah menjadi text yang tidak bisa langsung dijumlahkan lagi.

Pilih round saat angkanya masih dipakai hitungan berikutnya; pilih to_char (atau pemformatan di sisi aplikasi) saat angkanya memang menuju tampilan akhir.

AVG Mengabaikan NULL — Termasuk dari Pembagi #

Kejutan kedua avg menyatu dengan sum: NULL tidak dihitung sebagai nol, dan lebih penting lagi, NULL juga tidak ikut sebagai pembagi:

sql avg-null.sql
-- AVG mengabaikan NULL: hanya baris dengan diskon terisi yang dihitung.
SELECT avg(diskon)    AS rata_diskon,
       count(diskon)  AS baris_dihitung
FROM penjualan;

-- Bukti persamaannya: avg(x) identik dengan sum(x) / count(x).
SELECT sum(diskon) / count(diskon) AS lewat_sum_dibagi_count
FROM penjualan;

-- Ganti pembaginya menjadi count(*) dan maknanya berubah total:
-- 45.00 dibagi 12 transaksi = rata-rata diskon jika NULL dianggap "tanpa diskon".
SELECT sum(diskon) / count(*) AS rata_terhadap_semua_transaksi
FROM penjualan;

Output yang Dihasilkan:

text
    rata_diskon     | baris_dihitung 
--------------------+----------------
 7.5000000000000000 |              6
(1 row)

 lewat_sum_dibagi_count 
------------------------
     7.5000000000000000
(1 row)

 rata_terhadap_semua_transaksi 
-------------------------------
            3.7500000000000000
(1 row)

Query pertama dan kedua menghasilkan angka yang identik: 7.5. Total diskon 45.00 dibagi 6 baris terisi — bukan dibagi 12. Inilah persamaan resminya: avg(x) setara dengan sum(x) / count(x), dan karena count(x) melewatkan NULL, pembagi otomatis menyesuaikan. Query ketiga sengaja memakai pembagi yang berbeda — count(*) — dan hasilnya anjlok menjadi 3.75: total diskon yang sama dibagi rata ke seluruh 12 transaksi. Keduanya benar; keduanya menjawab pertanyaan berbeda. “Rata-rata diskon transaksi yang berdiskon” (7.5) lawan “rerata diskon efektif di seluruh transaksi” (3.75) adalah keputusan bisnis, dan avg memihak yang pertama tanpa bertanya.

Rata-rata Tertimbang #

Kejutan ketiga muncul saat setiap nilai punya bobot. Rata-rata sederhana avg(harga) memperlakukan harga 15000 yang terjual 40 unit sama beratnya dengan harga 90000 yang terjual 6 unit — seolah setiap harga muncul satu kali:

-- Rata-rata sederhana: setiap harga satuan dianggap setara bobotnya.
SELECT avg(harga) AS rata_harga_satuan
FROM penjualan;

-- Rata-rata tertimbang: setiap harga "ditimbang" dengan unit yang terjual —
-- total rupiah dibagi total unit. Produk murah yang terjual banyak menarik
-- hasil ke bawah.
SELECT sum(jumlah * harga) / sum(jumlah) AS rata_biaya_per_item
FROM penjualan;

-- Keduanya berdampingan: selisih 52083.33 lawan 38091.29 adalah bukti
-- bahwa "rata-rata apa" harus diputuskan sadar, bukan kebetulan.
SELECT round(avg(harga), 2)                        AS rata_sederhana,
       round(sum(jumlah * harga) / sum(jumlah), 2) AS rata_tertimbang,
       sum(jumlah)                                 AS total_unit
FROM penjualan;

Output yang Dihasilkan:

text
 rata_harga_satuan  
--------------------
 52083.333333333333
(1 row)

 rata_biaya_per_item 
---------------------
  38091.286307053942
(1 row)

 rata_sederhana | rata_tertimbang | total_unit 
----------------+-----------------+------------
       52083.33 |        38091.29 |        241
(1 row)

Mari kita bedah pola rata-rata tertimbangnya:

  1. sum(jumlah * harga) menjumlahkan nilai rupiah seluruh transaksi — pembilangnya: 9180000.00.
  2. sum(jumlah) menjumlahkan seluruh unit terjual — pembaginya: 241.
  3. Pembagian keduanya menghasilkan 38091.286307053942 — biaya rata-rata per satu item yang benar-benar terjual.

Hasilnya jauh berbeda dari avg(harga) yang menghasilkan 52083.33. Selisahnya ada karena Gula Merah yang murah (15000) menyumbang 105 unit — menarik biaya per item ke bawah — sedangkan rata-rata sederhana sama sekali tidak melihat unit. Pola sum(a × b) / sum(b) ini berlaku umum untuk semua rata-rata berbobot: nilai rapor tertimbang SKS, harga pokok rata-rata tertimbang volume pembelian, sampai durasi rata-rata tertimbang jumlah request.

Total dan rata-rata kini dikuasai; dua pertanyaan laporan tersisa berada di kutub berlawanan: nilai terkecil dan terbesar. Chapter berikutnya membahas min dan max — dari angka, teks, sampai tanggal: MIN dan MAX .

Referensi #

Berkas Sumber

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

avg-dasar.sql

Unduh berkas
sql
-- AVG = total nilai dibagi banyaknya nilai: 241 unit dibagi 12 transaksi.
SELECT avg(jumlah) AS rata_mentah
FROM penjualan;

-- Tipe hasilnya numeric — bukan integer — meski kolomnya integer.
SELECT pg_typeof(avg(jumlah)) AS tipe_hasil
FROM penjualan;

-- Dua cara merapikan hasilnya:
-- round() membulatkan nilainya (tetap numeric),
-- to_char() hanya memformat tampilan (hasilnya text).
SELECT round(avg(jumlah), 2)                AS rata_dibulatkan,
       to_char(avg(jumlah), 'FM9999990.00') AS rata_tampilan
FROM penjualan;

avg-null.sql

Unduh berkas
sql
-- AVG mengabaikan NULL: hanya baris dengan diskon terisi yang dihitung.
SELECT avg(diskon)    AS rata_diskon,
       count(diskon)  AS baris_dihitung
FROM penjualan;

-- Bukti persamaannya: avg(x) identik dengan sum(x) / count(x).
SELECT sum(diskon) / count(diskon) AS lewat_sum_dibagi_count
FROM penjualan;

-- Ganti pembaginya menjadi count(*) dan maknanya berubah total:
-- 45.00 dibagi 12 transaksi = rata-rata diskon jika NULL dianggap "tanpa diskon".
SELECT sum(diskon) / count(*) AS rata_terhadap_semua_transaksi
FROM penjualan;

avg-tertimbang.sql

Unduh berkas
sql
-- Rata-rata sederhana: setiap harga satuan dianggap setara bobotnya.
SELECT avg(harga) AS rata_harga_satuan
FROM penjualan;

-- Rata-rata tertimbang: setiap harga "ditimbang" dengan unit yang terjual —
-- total rupiah dibagi total unit. Produk murah yang terjual banyak menarik
-- hasil ke bawah.
SELECT sum(jumlah * harga) / sum(jumlah) AS rata_biaya_per_item
FROM penjualan;

-- Keduanya berdampingan: selisih 52083.33 lawan 38091.29 adalah bukti
-- bahwa "rata-rata apa" harus diputuskan sadar, bukan kebetulan.
SELECT round(avg(harga), 2)                        AS rata_sederhana,
       round(sum(jumlah * harga) / sum(jumlah), 2) AS rata_tertimbang,
       sum(jumlah)                                 AS total_unit
FROM penjualan;

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;