Menghitung Average Order Value (AOV) di SQL: Panduan Lengkap
Blog/Tutorial SQL/Menghitung Average Order Value (AOV) di SQL: Panduan Lengkap

Menghitung Average Order Value (AOV) di SQL: Panduan Lengkap

BimaBima
·20 September 2026·10 menit baca

Penulis

Bima

Bima

Founder & Data Professional

Bagikan

TL;DR

Average order value atau AOV adalah rata-rata nilai belanja per satu transaksi, dihitung dengan cara total omzet dibagi jumlah order unik. Di SQL rumusnya SUM(nilai) dibagi COUNT(DISTINCT order_id). Kesalahan paling umum adalah bagi pakai COUNT(*) atau jumlah baris, bukan jumlah order unik, yang bikin angka AOV jadi terlalu kecil kalau satu order punya banyak item. Buat data yang distribusinya miring, cek juga median pakai PERCENTILE_CONT biar nggak ketipu segelintir order besar.

Average order value atau AOV adalah rata-rata nilai belanja dalam satu transaksi. Di SQL, rumus dasarnya total omzet dibagi jumlah order unik: SUM(nilai) / COUNT(DISTINCT order_id).

Kelihatan simpel. Tapi ini salah satu metrik yang paling sering dihitung salah, dan salahnya nggak kelihatan sampai ada yang ngecek ulang. Bagi pakai jumlah baris, lupa data ada di level item, atau ketipu rata-rata yang ketarik order borongan.

Di bawah ini cara ngitung AOV yang benar di SQL, dari rumus dasar sampai pecah per bulan dan per channel, plus jebakan mean vs median yang bikin banyak laporan nyesatin.

Apa itu average order value?

AOV adalah rata-rata uang yang masuk tiap satu order terjadi. Hitungannya total omzet dibagi jumlah transaksi dalam periode tertentu. Kalau omzet bulan ini Rp 100 juta dari 1.000 order, AOV kamu Rp 100.000 per order.

Metrik ini penting karena dia salah satu dari tiga tuas utama pertumbuhan omzet: jumlah pelanggan, frekuensi belanja, dan nilai per order. Naikin AOV artinya tiap pelanggan yang udah datang belanja lebih banyak, tanpa kamu perlu cari pelanggan baru.

AOV juga bahan dasar buat ngitung metrik yang lebih dalam kayak lifetime value. Aku bahas kaitannya di menghitung customer lifetime value dengan SQL. Definisi ringkasnya ada di glossary average order value.

Gimana rumus AOV dasar di SQL?

Anggap kamu punya tabel transaksi dengan satu baris per order, kolomnya order_id, tanggal, dan total_harga. Rumusnya langsung.

SELECT
  SUM(total_harga)                                  AS total_omzet,
  COUNT(DISTINCT order_id)                           AS jumlah_order,
  ROUND(SUM(total_harga) * 1.0
        / COUNT(DISTINCT order_id), 0)               AS aov
FROM transaksi;

Dua hal yang gampang kelupaan di sini. Pertama, * 1.0 maksa SQL ngitung pakai desimal. Tanpa itu, sebagian database motong hasil bagi jadi bilangan bulat dan angkanya jadi salah.

Kedua, COUNT(DISTINCT order_id), bukan COUNT(*). Di tabel yang satu baris satu order sih hasilnya sama. Tapi kebiasaan pakai DISTINCT nyelametin kamu pas datanya ternyata di level item. Kalau COUNT dan GROUP BY masih bikin bingung, cek dulu fungsi COUNT dan fungsi SUM.

Gimana kalau data ada di level item?

Ini sumber kesalahan AOV paling umum. Banyak tabel transaksi sebenernya di level item, satu baris buat tiap produk dalam satu order. Order dengan 5 produk berarti 5 baris.

Kalau kamu langsung bagi omzet pakai COUNT(*), pembaginya 5x lebih besar dari jumlah order sebenernya, dan AOV kamu anjlok jadi seperlima. Solusinya: agregasi dulu ke level order pakai CTE, baru rata-ratain.

WITH per_order AS (
  SELECT
    order_id,
    SUM(harga * qty) AS nilai_order
  FROM item_transaksi
  GROUP BY order_id
)
SELECT
  ROUND(AVG(nilai_order), 0)                                       AS aov,
  ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY nilai_order), 0) AS median_order
FROM per_order;

CTE per_order ngerangkum tiap order jadi satu baris dengan total nilainya. Baru di luar, AVG ngerata-ratain nilai per order yang udah bener. Ini AOV yang bisa kamu percaya.

Gimana cara lihat AOV per bulan?

AOV satu angka doang nggak banyak cerita. Yang berguna itu trennya: naik, turun, atau datar. Pecah per bulan pakai DATE_TRUNC.

SELECT
  DATE_TRUNC('month', tanggal)                     AS bulan,
  COUNT(DISTINCT order_id)                          AS jumlah_order,
  ROUND(SUM(total_harga) * 1.0
        / COUNT(DISTINCT order_id), 0)              AS aov
FROM transaksi
GROUP BY DATE_TRUNC('month', tanggal)
ORDER BY bulan;

Baca hasilnya bareng kolom jumlah order. AOV yang naik sambil order tetap itu kabar bagus. Tapi AOV naik gara-gara satu bulan ada order borongan gede, itu bukan tren, cuma kebetulan. Makanya kolom jumlah order penting buat konteks.

Mean vs median: kenapa AOV kamu bisa nyesatin

AVG itu rata-rata biasa, dan dia gampang ketarik nilai ekstrem. Beberapa order borongan bernilai jutaan bisa naikin AOV padahal mayoritas pelanggan belanja kecil. Di titik ini, median lebih jujur.

Median itu nilai order yang persis di tengah kalau semua order diurut. Setengah order di bawahnya, setengah di atasnya. Nilai ekstrem nggak nariknya.

SELECT
  ROUND(AVG(total_harga), 0)                                        AS mean_aov,
  ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total_harga), 0) AS median_aov
FROM transaksi;

Aturan bacanya: kalau mean jauh di atas median, distribusi order kamu miring. Ada segelintir order besar yang narik rata-rata, dan angka mean sendirian bakal bikin kamu overestimate kebiasaan belanja pelanggan biasa.

Contoh kasus: AOV toko_berkah online

Data 12 bulan toko_berkah versi online: 8.400 order, omzet Rp 731 juta. Semua angka di bawah dari dataset ngulikdata.

AOV mean-nya keluar di Rp 87.000. Kelihatan sehat. Tapi waktu aku hitung median, angkanya cuma Rp 62.000. Selisih 40% antara mean dan median itu sinyal jelas ada yang narik rata-rata.

Setelah aku telusuri, ternyata 3% order teratas adalah pembelian borongan dari warung yang kulakan. Nilainya di atas Rp 500.000 per order. Sedikit jumlahnya, tapi cukup buat naikin mean.

UkuranNilaiArtinya
Mean AOV (semua)Rp 87.000Ketarik order borongan
Median AOVRp 62.000Nilai order tipikal
Mean AOV tanpa 3% teratasRp 71.000Lebih dekat kenyataan ritel

Keputusan yang berubah: tim marketing tadinya mau pasang gratis ongkir minimal Rp 90.000, ngikut mean. Angka itu di atas AOV pelanggan ritel biasa, jadi bakal sepi yang kena. Setelah lihat median Rp 62.000, ambang gratis ongkirnya diturunin ke Rp 75.000, cukup di atas median buat dorong belanja tambahan tanpa kejauhan.

Pecah AOV per channel

AOV total nyembunyiin perbedaan antar channel. Order dari aplikasi, marketplace, dan WhatsApp bisa punya pola nilai yang beda jauh. Pecah biar kelihatan.

SELECT
  channel,
  COUNT(DISTINCT order_id)                          AS jumlah_order,
  ROUND(SUM(total_harga) * 1.0
        / COUNT(DISTINCT order_id), 0)              AS aov
FROM transaksi
GROUP BY channel
ORDER BY aov DESC;

Buat ngitung kontribusi omzet tiap channel dalam persen, lanjut ke cara menghitung persentase di SQL.

Kesalahan umum waktu ngitung AOV

Bagi pakai COUNT(*) di data level item. Ini bikin AOV kamu jadi sepersekian dari yang sebenernya. Selalu COUNT(DISTINCT order_id) atau agregasi dulu ke level order.

Lupa kali 1.0 atau CAST. Di beberapa database, bagi dua integer motong desimalnya. Rp 87.000 bisa keluar jadi angka bulat yang salah. Paksa desimal dulu.

Cuma lihat mean. Tanpa median, kamu nggak tahu apakah AOV kamu ketarik segelintir order besar. Hitung dua-duanya.

Masukin order yang dibatalin atau di-refund. Kalau tabel transaksi nyimpen status, filter dulu yang gagal. AOV dari order yang nggak jadi itu bukan AOV.

Banding AOV antar periode dengan panjang beda. AOV bulan penuh nggak bisa langsung dibanding sama AOV setengah bulan berjalan. Samain dulu jendelanya.

FAQ

Apa itu average order value (AOV)?

Average order value atau AOV adalah rata-rata nilai belanja dalam satu transaksi. Cara hitungnya total omzet dibagi jumlah order unik dalam periode tertentu. Kalau omzet bulananmu Rp 100 juta dari 1.000 order, AOV kamu Rp 100.000. Angka ini kepakai buat ngukur seberapa besar rata-rata pelanggan belanja sekali datang, dan jadi metrik dasar buat strategi upsell dan bundling.

Gimana rumus AOV di SQL?

Rumus dasarnya SUM(nilai_transaksi) dibagi COUNT(DISTINCT order_id). Pakai COUNT(DISTINCT order_id), bukan COUNT(*), supaya satu order dihitung sekali walaupun punya banyak item. Kalau datamu di level item, agregasi dulu jadi per order pakai CTE, baru rata-ratain. Tambahin ROUND biar rapi, dan kali 1.0 supaya SQL nggak motong desimal jadi bilangan bulat.

Kenapa AOV saya kelihatan terlalu kecil?

Paling sering karena kamu bagi total omzet pakai jumlah baris, bukan jumlah order unik. Kalau tabel kamu di level item, satu order dengan 5 item kehitung 5 kali, jadi pembaginya membengkak dan AOV-nya anjlok. Pakai COUNT(DISTINCT order_id) buat pembagi, atau agregasi dulu ke level order sebelum ngerata-ratain nilainya.

Lebih baik pakai mean atau median buat AOV?

Mean gampang dihitung dan cukup buat pantau tren, tapi dia gampang ketarik segelintir order besar. Kalau ada beberapa pesanan borongan bernilai jutaan, mean AOV kamu keliatan tinggi padahal mayoritas order kecil. Median lebih jujur nunjukin nilai order tipikal. Idealnya hitung dua-duanya. Kalau mean jauh di atas median, distribusi order kamu miring.

Gimana cara naikin AOV?

Tiga cara yang paling umum: bundling produk biar sekali beli dapat lebih banyak, kasih gratis ongkir dengan minimum belanja tertentu, dan upsell produk pelengkap pas checkout. Sebelum ngukur efeknya, tetapin dulu baseline AOV pakai SQL. Setelah kampanye jalan, bandingin AOV per periode buat lihat apakah beneran naik atau cuma kebetulan ada order besar.

Penutup

Yang perlu kamu bawa soal AOV:

  • Rumusnya SUM(nilai) dibagi COUNT(DISTINCT order_id), jangan bagi pakai jumlah baris
  • Kalau data di level item, agregasi dulu ke level order pakai CTE
  • Selalu cek median di samping mean biar nggak ketipu order borongan

Coba jalanin query pertama di data kamu, terus bandingin mean sama mediannya. Kalau selisihnya jauh, kamu baru nemu insight yang mean sendirian sembunyiin. Latihan query kayak gini bisa kamu lanjut di NgulikSQL, dan buat dasar agregasi cek GROUP BY dan HAVING. Referensi fungsi agregat ada di dokumentasi PostgreSQL.

Coba Langsung

Mau praktek langsung? Mulai latihan SQL gratis

Latihan interaktif, langsung di browser.

Buka NgulikSQL →
Bagikan:
Bima
Ditulis oleh

Bima

Founder & Data Professional

Founder Ngulik Data. Passionate about making data analysis accessible for everyone.

Artikel terkait

Semi Join dan Anti Join di SQL: Filter Pakai Tabel Lain
Tutorial SQL
14 November 2026•8 menit baca

Semi Join dan Anti Join di SQL: Filter Pakai Tabel Lain

Semi join ngambil baris yang punya pasangan di tabel lain, anti join ngambil yang nggak punya. Keduanya filter, bukan gabung kolom. Ini cara nulisnya di SQL pakai EXISTS dan NOT EXISTS.

BimaBima
Crosstab (Pivot) di PostgreSQL: Ubah Baris Jadi Kolom
Tutorial SQL
2 November 2026•11 menit baca

Crosstab (Pivot) di PostgreSQL: Ubah Baris Jadi Kolom

Crosstab di PostgreSQL mivot data dari format panjang (baris) jadi format lebar (kolom), mirip pivot table Excel. Butuh extension tablefunc, dan ada alternatif pakai CASE atau FILTER kalau kolomnya tetap.

BimaBima
INSERT ON CONFLICT di PostgreSQL
Tutorial SQL
25 Oktober 2026•11 menit baca

INSERT ON CONFLICT di PostgreSQL

INSERT ON CONFLICT bikin PostgreSQL insert baris baru atau update yang udah ada dalam satu perintah, tanpa error gara-gara duplikat primary key.

BimaBima
Kembali ke Blog
Ngulik Data logoNgulik Data

Platform edukasi data lengkap untuk professionals Indonesia. Belajar SQL, Data Analysis, dan lebih banyak lagi dengan praktek langsung dan feedback real-time.

© 2026 Ngulik Data. Semua hak dilindungi.

TAUTAN
BantuanHargaDatasetBlogAfiliasi
LEGAL
Syarat & KetentuanKebijakan Privasi
Ngulik Data
DatasetLeaderboardBlogStore