Foto oleh Brett Sayles via Pexels

Fungsi agregat adalah fungsi yang menghitung satu nilai ringkasan dari sekumpulan baris, seperti total, rata-rata, atau jumlah data. COUNT, SUM, AVG, MIN, dan MAX adalah lima fungsi agregat paling sering dipakai di SQL Server, dan hampir setiap laporan bisnis pasti melibatkan setidaknya satu di antaranya.

Artikel ini membahas masing-masing fungsi beserta perbedaan penting antara COUNT(*) dan COUNT(kolom) yang sering disalahpahami dan menyebabkan angka laporan yang keliru.

COUNT: Menghitung Jumlah Baris

COUNT(*) vs COUNT(kolom)

-- Menghitung seluruh baris, termasuk yang punya NULL SELECT COUNT(*) FROM Orders; -- Menghitung baris yang kolom ShippedDate-nya TIDAK NULL SELECT COUNT(ShippedDate) FROM Orders; -- Expected: jika ada 100 order namun 20 belum dikirim (ShippedDate NULL), -- COUNT(*) = 100, COUNT(ShippedDate) = 80

COUNT(*) menghitung seluruh baris tanpa peduli nilai NULL, sementara COUNT(nama_kolom) hanya menghitung baris yang nilainya tidak NULL pada kolom tersebut. Perbedaan ini sering jadi sumber bug ketika developer mengira keduanya selalu sama.

SUM, AVG, MIN, MAX

Ringkasan statistik transaksi yang sudah terkirim

SELECT SUM(TotalAmount) AS TotalPenjualan, AVG(TotalAmount) AS RataRataTransaksi, MIN(TotalAmount) AS TransaksiTerkecil, MAX(TotalAmount) AS TransaksiTerbesar FROM Orders WHERE Status = 'Delivered'; -- Expected: empat angka ringkasan dalam satu baris hasil, -- hanya dihitung dari order yang sudah terkirim

Fungsi Agregat Mengabaikan NULL

AVG dan COUNT mengabaikan baris NULL secara otomatis

-- Tabel dengan 5 baris Discount: 10, NULL, 20, NULL, 30 SELECT AVG(Discount) AS RataRataDiskon FROM Products; -- Expected: (10+20+30)/3 = 20, BUKAN (10+0+20+0+30)/5 = 12 SELECT COUNT(Discount) AS JumlahBerDiskon FROM Products; -- Expected: 3, bukan 5

SUM, AVG, MIN, dan MAX secara otomatis mengabaikan baris dengan nilai NULL pada kolom yang dihitung. Jika sebuah table memiliki 5 baris namun 2 di antaranya NULL pada kolom tertentu, AVG hanya akan membagi total dengan 3, bukan 5 — hal ini penting dipahami agar hasil rata-rata tidak disalahartikan.

Menangani NULL Secara Eksplisit dengan ISNULL atau COALESCE

ISNULL mengubah perilaku perhitungan rata-rata secara signifikan

-- Memperlakukan NULL sebagai 0 secara eksplisit SELECT AVG(ISNULL(Discount, 0)) AS RataRataDiskonTermasukNol FROM Products; -- Expected: (10+0+20+0+30)/5 = 12, HASIL BERBEDA dari AVG biasa

Ketika kebutuhan bisnis mengharuskan NULL diperlakukan sebagai nol (misalnya produk tanpa diskon dianggap diskon 0%, bukan dikecualikan dari perhitungan), ISNULL atau COALESCE perlu dipakai secara eksplisit sebelum fungsi agregat.

Menggabungkan Beberapa Fungsi Agregat Sekaligus

Lima fungsi agregat dipakai bersamaan dalam satu SELECT

SELECT ProductID, COUNT(*) AS JumlahTransaksi, SUM(Quantity) AS TotalUnit, AVG(Quantity) AS RataRataUnit, MIN(Quantity) AS UnitTerkecil, MAX(Quantity) AS UnitTerbesar FROM OrderDetails GROUP BY ProductID; -- Expected: satu baris per ProductID berisi lima -- ringkasan statistik sekaligus dalam satu query

Dalam praktiknya, laporan bisnis jarang hanya membutuhkan satu fungsi agregat saja. Menggabungkan COUNT, SUM, AVG, MIN, dan MAX dalam satu query jauh lebih efisien dibanding menjalankan lima query terpisah, karena mesin database hanya perlu membaca data sumber satu kali untuk menghasilkan seluruh ringkasan tersebut.

Perlu diingat bahwa AVG menghitung rata-rata hanya dari baris yang nilainya tidak NULL, sehingga jika sebagian baris memiliki Quantity NULL, hasil AVG bisa berbeda dari ekspektasi jika dibandingkan dengan menghitung manual SUM dibagi COUNT(*) yang menyertakan seluruh baris termasuk yang NULL.

Perilaku Fungsi Agregat Terhadap Nilai NULL

COUNT(*) vs COUNT(kolom), dan AVG/SUM yang mengabaikan NULL

-- Data: Orders punya 5 baris, 2 di antaranya Discount = NULL SELECT COUNT(*) AS TotalBaris, COUNT(Discount) AS BarisDenganDiscount, AVG(Discount) AS RataRataDiscount, SUM(Discount) AS TotalDiscount FROM Orders; -- Hasil, contoh: -- TotalBaris=5, BarisDenganDiscount=3, -- RataRataDiscount dihitung HANYA dari 3 baris, bukan 5

Seluruh fungsi agregat di SQL Server kecuali COUNT(*) secara otomatis mengabaikan baris dengan nilai NULL pada kolom yang dihitung. Ini berarti AVG(Discount) tidak membagi total nilai dengan jumlah seluruh baris, melainkan hanya dengan jumlah baris yang memiliki nilai Discount bukan NULL; kesalahpahaman terhadap perilaku ini bisa membuat hasil rata-rata terlihat lebih tinggi dari yang sebenarnya diharapkan jika asumsinya adalah NULL dihitung sebagai nol.

Perbedaan antara COUNT(*) dan COUNT(nama_kolom) juga sering tertukar: COUNT(*) menghitung seluruh baris tanpa peduli ada NULL atau tidak pada kolom mana pun, sedangkan COUNT(nama_kolom) hanya menghitung baris yang nilainya pada kolom tersebut bukan NULL. Jika kebutuhannya adalah menghitung berapa banyak pelanggan yang sudah mengisi nomor telepon, COUNT(Phone) adalah pilihan yang tepat, bukan COUNT(*).

Untuk kasus yang membutuhkan NULL diperlakukan sebagai nol dalam perhitungan rata-rata, misalnya memastikan AVG tetap membagi dengan seluruh jumlah baris, fungsi ISNULL atau COALESCE dapat dipakai untuk mengubah NULL menjadi 0 terlebih dahulu di dalam ekspresi, seperti AVG(ISNULL(Discount, 0)), sehingga hasil perhitungan mencerminkan maksud yang sebenarnya diinginkan.

Perbedaan COUNT(*), COUNT(kolom), dan COUNT(DISTINCT kolom)

COUNT(*) vs COUNT(kolom) vs COUNT(DISTINCT kolom)

-- Tiga variasi COUNT dengan hasil yang bisa berbeda-beda SELECT COUNT(*) AS TotalBaris, COUNT(NomorTelepon) AS BarisDenganTelepon, COUNT(DISTINCT Kota) AS JumlahKotaUnik FROM dbo.Pelanggan; -- Misal table Pelanggan punya 100 baris, 15 di antaranya -- NomorTelepon bernilai NULL, dan pelanggan tersebar di 8 kota unik -- Hasil: -- TotalBaris: 100 -- BarisDenganTelepon: 85 -- JumlahKotaUnik: 8

COUNT(*) menghitung jumlah seluruh baris dalam grup atau table tanpa memandang isi kolom apa pun, termasuk baris yang semua kolomnya bernilai NULL, sehingga hasilnya selalu sama dengan jumlah baris fisik yang memenuhi kondisi WHERE. Sebaliknya, COUNT(nama_kolom) hanya menghitung baris yang nilai kolom tersebut tidak NULL, karena fungsi agregat pada SQL Server secara konsisten mengabaikan NULL kecuali pada COUNT(*).

Perbedaan ini sering menjadi sumber kebingungan ketika seseorang mengharapkan COUNT(NomorTelepon) sama dengan jumlah total baris, padahal pada kenyataannya angka tersebut hanya mencerminkan baris yang kolom NomorTelepon-nya terisi. Memilih antara keduanya harus disesuaikan dengan pertanyaan bisnis yang sebenarnya: jika pertanyaannya 'berapa total pelanggan', pakai COUNT(*); jika pertanyaannya 'berapa pelanggan yang punya nomor telepon', pakai COUNT(NomorTelepon), karena kesalahan memilih keduanya bisa membuat laporan kelengkapan data menjadi menyesatkan.

COUNT(DISTINCT kolom) menambahkan satu lapis logika lagi dengan menghitung jumlah nilai unik pada kolom tersebut, mengabaikan duplikat dan NULL sekaligus, seperti menghitung berapa kota berbeda yang dihuni pelanggan tanpa peduli berapa banyak pelanggan di tiap kota. Operasi DISTINCT di dalam COUNT memerlukan langkah pengurutan atau hashing tambahan secara internal, sehingga pada table sangat besar, COUNT(DISTINCT kolom) cenderung lebih berat secara komputasi dibanding COUNT biasa.

Menggabungkan Nilai Teks per Grup dengan STRING_AGG

STRING_AGG menggabungkan string per grup

-- Menggabungkan daftar produk yang dibeli tiap pelanggan jadi satu string SELECT PelangganID, STRING_AGG(NamaProduk, ', ') AS DaftarProduk FROM dbo.DetailPesanan GROUP BY PelangganID; -- Hasil, contoh: -- PelangganID DaftarProduk -- 101 Mouse, Keyboard, Monitor -- 102 Headset, Webcam

STRING_AGG, tersedia sejak SQL Server 2017, melengkapi fungsi agregat numerik seperti SUM dan COUNT dengan kemampuan menggabungkan nilai-nilai teks dari banyak baris dalam satu grup menjadi satu string tunggal yang dipisahkan pemisah tertentu, seperti koma. Fungsi ini menggantikan trik lama yang memakai FOR XML PATH untuk mencapai hasil serupa, dengan sintaks yang jauh lebih sederhana dan mudah dibaca.

Seperti fungsi agregat lainnya, STRING_AGG wajib dipakai bersama GROUP BY ketika hasil yang diinginkan adalah satu baris gabungan per kelompok, dan secara otomatis mengabaikan nilai NULL pada kolom yang digabungkan. STRING_AGG juga mendukung klausa WITHIN GROUP (ORDER BY ...) untuk menentukan urutan kemunculan nilai di dalam string hasil gabungan, sehingga daftar produk misalnya bisa ditampilkan terurut alfabetis alih-alih mengikuti urutan baris asli yang tidak pasti.

Kesalahan Umum yang Sering Terjadi

Mengira COUNT(*) dan COUNT(kolom) selalu menghasilkan angka yang sama

Keduanya hanya identik jika kolom yang dihitung tidak pernah bernilai NULL. Pada kolom yang memperbolehkan NULL, hasil keduanya bisa berbeda signifikan dan menyebabkan laporan yang keliru.

Tidak menyadari AVG mengabaikan NULL, bukan menganggapnya nol

Developer yang mengharapkan AVG memperlakukan NULL sebagai 0 akan mendapat hasil yang lebih tinggi dari ekspektasi, karena baris NULL sebenarnya dikecualikan sepenuhnya dari perhitungan rata-rata.

Kapan Pendekatan Ini Tidak Cocok Dipakai

Menggunakan ISNULL untuk memaksa NULL menjadi 0 pada semua kasus terdengar aman, namun ini bisa mengaburkan makna data asli — misalnya 'diskon belum pernah diatur' berbeda maknanya secara bisnis dibanding 'diskon sengaja diset 0 persen'. Pertimbangkan makna bisnis dari NULL sebelum memutuskan menggantinya dengan nilai default.

Ringkasan

Kelima fungsi agregat ini menjadi dasar dari hampir seluruh laporan bisnis yang dibuat di atas SQL Server. Memahami bagaimana masing-masing memperlakukan nilai NULL akan mencegah kesalahan interpretasi data pada laporan yang dihasilkan.

Seri Belajar SQL Server:

← Sebelumnya: Filtering Hasil Group dengan HAVING

→ Selanjutnya: Menggabungkan Query dengan UNION dan UNION ALL