Agregasi dan WITH

Pengelompokan otomatis, collect, dan cara berpikir pipeline dengan WITH.

Daftar isi

Agregasi (aggregation) menggabungkan banyak baris hasil kueri menjadi satu nilai ringkas, seperti jumlah, rata-rata, atau deretan nilai. Topik ini membahas fungsi agregat bawaan Cypher, pengelompokan otomatis berdasarkan kunci non-agregat, fungsi collect untuk membentuk list, yaitu deretan nilai, serta klausa WITH yang bekerja sebagai pipeline antarbagian kueri. Setelah topik ini dibaca, kueri bertingkat dengan pola MATCH, WITH agregasi, WHERE, dan RETURN dapat disusun serta dijalankan pada dataset jaringan sosial modul ini.

Dataset jaringan sosial#

Seluruh contoh pada modul ini menggunakan satu dataset kecil yang sama, yaitu jaringan sosial berisi tujuh node berlabel :Person dengan properti name dan kota, ditambah dua node :Hobi. Relasi :KENAL membentuk rantai Eko, Fajar, Gita, Hana, ditambah cabang Eko, Iwan, Hana, serta dua cabang pendek menuju Jihan dan Rara. Hanya Hana dan Fajar yang dihubungkan ke hobi melalui relasi :PUNYA_HOBI. Properti kota, relasi :KENAL, dan sebaran hobi yang tidak merata menyediakan bahan pengelompokan, penghitungan, dan pengumpulan nilai pada topik ini.

Skrip setup.cypher berikut membangun seluruh dataset dengan MERGE, sehingga aman dijalankan berulang tanpa menduplikasi data. Cara termudah memuat skrip tersebut adalah mengalirkannya ke cypher-shell melalui pipa, atau mengeksekusinya dari Neo4j Browser.

cypher setup.cypher
// Dataset modul Pola kueri: jaringan sosial dengan relasi KENAL dan hobi.
// Skrip ini idempoten, sehingga aman dijalankan lebih dari satu kali.

// Tujuh orang beserta kota asalnya
MERGE (p:Person {name: 'Eko'}) SET p.kota = 'Bandung';
MERGE (p:Person {name: 'Fajar'}) SET p.kota = 'Bandung';
MERGE (p:Person {name: 'Gita'}) SET p.kota = 'Semarang';
MERGE (p:Person {name: 'Hana'}) SET p.kota = 'Surabaya';
MERGE (p:Person {name: 'Iwan'}) SET p.kota = 'Semarang';
MERGE (p:Person {name: 'Jihan'}) SET p.kota = 'Bandung';
MERGE (p:Person {name: 'Rara'}) SET p.kota = 'Yogyakarta';

// Dua hobi
MERGE (h:Hobi {name: 'Fotografi'});
MERGE (h:Hobi {name: 'Bersepeda'});

// Rantai KENAL, kedua ujung diikat variabel agar MERGE hanya menangani relasinya
MATCH (a:Person {name: 'Eko'}), (b:Person {name: 'Fajar'}) MERGE (a)-[:KENAL]->(b);
MATCH (a:Person {name: 'Fajar'}), (b:Person {name: 'Gita'}) MERGE (a)-[:KENAL]->(b);
MATCH (a:Person {name: 'Gita'}), (b:Person {name: 'Hana'}) MERGE (a)-[:KENAL]->(b);
MATCH (a:Person {name: 'Eko'}), (b:Person {name: 'Iwan'}) MERGE (a)-[:KENAL]->(b);
MATCH (a:Person {name: 'Iwan'}), (b:Person {name: 'Hana'}) MERGE (a)-[:KENAL]->(b);
MATCH (a:Person {name: 'Gita'}), (b:Person {name: 'Jihan'}) MERGE (a)-[:KENAL]->(b);
MATCH (a:Person {name: 'Fajar'}), (b:Person {name: 'Rara'}) MERGE (a)-[:KENAL]->(b);

// Hobi hanya dimiliki sebagian orang, sehingga null muncul secara alami
MATCH (a:Person {name: 'Hana'}), (b:Hobi {name: 'Fotografi'}) MERGE (a)-[:PUNYA_HOBI]->(b);
MATCH (a:Person {name: 'Fajar'}), (b:Hobi {name: 'Bersepeda'}) MERGE (a)-[:PUNYA_HOBI]->(b);
bash
# tidak-dijalankan
# memuat dataset melalui cypher-shell, kata sandi diminta interaktif
cypher-shell -a bolt://localhost:7687 -u neo4j -f setup.cypher

Fungsi agregat#

Fungsi agregat menerima satu ekspresi dari setiap baris hasil, lalu mengembalikan satu nilai untuk sekumpulan baris. Fungsi count() menghitung banyaknya baris atau nilai yang tidak null, sedangkan bentuk count(DISTINCT x) hanya menghitung nilai yang saling berbeda. Kueri berikut menghitung node :Person dan banyaknya kota unik yang tersimpan pada properti kota.

cypher
MATCH (p:Person)
RETURN count(p) AS jumlah_orang, count(DISTINCT p.kota) AS jumlah_kota;
text Keluaran
jumlah_orang, jumlah_kota
7, 4

Keluarannya satu baris karena tidak ada ekspresi non-agregat pada RETURN, sehingga seluruh baris diperlakukan sebagai satu kelompok. Fungsi lainnya dirangkum pada tabel berikut.

FungsiNilai yang dihasilkan
count(x)banyaknya baris atau nilai yang tidak null
sum(x)penjumlahan seluruh nilai numerik
avg(x)rata-rata nilai numerik, berupa bilangan pecahan
min(x)nilai terkecil pada kelompoknya
max(x)nilai terbesar pada kelompoknya
collect(x)list berisi nilai x dari setiap baris

Fungsi sum(), avg(), min(), dan max() membutuhkan kolom numerik, dan dataset ini tidak menyimpan angka sebagai properti. Penerapan keempatnya ditunjukkan pada bagian tentang WITH, karena angka yang dihitung dari pola baru tersedia sebagai kolom setelah agregasi di tengah kueri.

Pengelompokan otomatis#

Cypher mengelompokkan baris secara otomatis tanpa menuntut klausa pengelompokan. Setiap ekspresi pada RETURN atau WITH yang bukan fungsi agregat menjadi kunci pengelompokan (grouping key), yaitu acuan untuk menentukan baris mana yang digabungkan. Baris-baris dengan nilai kunci yang sama membentuk satu kelompok, dan setiap kelompok menghasilkan satu baris keluaran. Kueri berikut menghitung banyaknya orang pada setiap kota.

cypher
MATCH (p:Person)
RETURN p.kota AS kota, count(p) AS jumlah
ORDER BY kota;
text Keluaran
kota, jumlah
"Bandung", 3
"Semarang", 2
"Surabaya", 1
"Yogyakarta", 1

Properti p.kota menjadi kunci pengelompokannya, sedangkan count(p) dihitung terpisah untuk setiap kelompok, sehingga Bandung yang dihuni Eko, Fajar, dan Jihan menghasilkan angka tiga. Perbandingan dengan kueri pada bagian sebelumnya memperlihatkan peran kunci tersebut, karena kueri tanpa ekspresi non-agregat menghasilkan satu baris, sedangkan penambahan p.kota memecah keluaran menjadi empat baris sesuai banyaknya kelompok. Apabila kendali eksplisit diinginkan, klausa GROUP BY yang diperkenalkan pada Neo4j 2026.07 dapat menuliskan kunci pengelompokan tersebut, sedangkan pengelompokan implisit tetap berlaku apabila klausa itu tidak ada.

Mengumpulkan nilai dengan collect#

Fungsi collect(x) mengumpulkan nilai x dari seluruh baris dalam satu kelompok menjadi satu list, sehingga menjadi penghubung antara baris hasil dengan list yang dapat diolah lebih lanjut. Argumennya dapat berupa properti maupun ekspresi apa pun yang dievaluasi untuk setiap baris dalam kelompoknya. Kedudukan inilah yang menjadikan collect kunci dari pola berpikir pipeline pada topik ini. Kueri berikut mengumpulkan nama-nama anggota setiap kota.

cypher
MATCH (p:Person)
WITH p ORDER BY p.name
RETURN p.kota AS kota, collect(p.name) AS anggota
ORDER BY kota;
text Keluaran
kota, anggota
"Bandung", ["Eko", "Fajar", "Jihan"]
"Semarang", ["Gita", "Iwan"]
"Surabaya", ["Hana"]
"Yogyakarta", ["Rara"]

Klausa WITH p ORDER BY p.name mengurutkan baris sebelum agregasi dilakukan, karena ORDER BY tidak dapat ditulis di dalam fungsi agregat seperti collect(). Fungsi collect menyusun list sesuai urutan baris yang diterimanya, sedangkan urutan baris hasil pencocokan itu sendiri tidak dijamin tanpa pengurutan, sehingga klausa ini diperlukan apabila isi list harus terulang sama pada setiap eksekusi. Keluaran memperlihatkan anggota list Bandung terurut menurut nama, yaitu Eko, Fajar, dan Jihan.

WITH sebagai pipeline#

Klausa WITH berperan seperti RETURN, tetapi hasilnya tidak mengakhiri kueri, melainkan menjadi masukan bagi bagian kueri berikutnya. Pada titik ini, agregasi dapat dilakukan di tengah kueri, variabel yang dibawa ke tahap berikutnya dapat dibatasi, dan hasil agregasi dapat disaring dengan WHERE, sesuatu yang tidak mungkin dilakukan pada WHERE sebelum agregasi karena fungsinya belum memiliki baris untuk dihitung. Kueri berikut menghitung banyaknya orang yang dikenal setiap orang, lalu mempertahankan baris yang jumlahnya dua atau lebih.

cypher
MATCH (p:Person)
OPTIONAL MATCH (p)-[:KENAL]->(lain:Person)
WITH p, count(lain) AS jumlah_kenalan
WHERE jumlah_kenalan >= 2
RETURN p.name AS nama, jumlah_kenalan
ORDER BY nama;
text Keluaran
nama, jumlah_kenalan
"Eko", 2
"Fajar", 2
"Gita", 2

Agregasi pada WITH mengelompokkan baris dengan aturan yang sama seperti pada RETURN, yaitu variabel non-agregat yang dicantumkan menjadi kuncinya, dan hanya p serta jumlah_kenalan yang tersisa pada tahap berikutnya. Pola OPTIONAL MATCH, yang dibahas pada topik tersendiri dalam modul ini, menjaga orang tanpa relasi :KENAL tetap menghasilkan baris, sedangkan count(lain) mengabaikan null sehingga jumlahnya menjadi nol. Klausa WHERE setelah WITH kemudian menyaring baris hasil agregasi, bukan baris pola.

Pola berpikir pipeline#

Kueri yang memanfaatkan WITH tersusun sebagai pipeline, yaitu rangkaian tahap yang menerima baris dari tahap sebelumnya, mengubah baris tersebut, lalu meneruskan hasilnya. Pola yang paling sering muncul adalah MATCH untuk menjaring baris pola, WITH untuk agregasi, WHERE untuk menyaring hasil agregasinya, kemudian MATCH atau RETURN. Kueri berikut menambahkan satu tahap MATCH setelah penyaringan, yaitu mencari orang yang dikenalnya berjumlah dua atau lebih dan memiliki hobi.

cypher
MATCH (p:Person)-[:KENAL]->(lain:Person)
WITH p, count(lain) AS jumlah_kenalan
WHERE jumlah_kenalan >= 2
MATCH (p)-[:PUNYA_HOBI]->(h:Hobi)
RETURN p.name AS nama, jumlah_kenalan, h.name AS hobi
ORDER BY nama;
text Keluaran
nama, jumlah_kenalan, hobi
"Fajar", 2, "Bersepeda"

Tahap WITH menyisakan Eko, Fajar, dan Gita, sedangkan MATCH kedua menghilangkan Eko dan Gita karena keduanya tidak memiliki relasi :PUNYA_HOBI, sehingga hanya Fajar yang lolos. Variabel p yang dibawa WITH menghubungkan kedua tahap, dan kolom jumlah_kenalan yang tersisa dapat langsung diagregasi lagi oleh fungsi agregat lain.

cypher
MATCH (p:Person)
OPTIONAL MATCH (p)-[:KENAL]->(lain:Person)
WITH p, count(lain) AS jumlah_kenalan
RETURN min(jumlah_kenalan) AS minimal, max(jumlah_kenalan) AS maksimal, sum(jumlah_kenalan) AS total, round(avg(jumlah_kenalan), 2) AS rata_rata;
text Keluaran
minimal, maksimal, total, rata_rata
0, 2, 7, 1.0

Fungsi avg() menghasilkan bilangan pecahan, dan round() membulatkannya agar keluarannya ringkas. Hasil ini memperlihatkan sifat pipeline yang berlapis, yaitu agregasi menghasilkan kolom baru yang dapat diagregasi lagi pada tahap berikutnya. Pipeline juga menjadi tempat yang wajar bagi collect, sebagaimana kueri berikut yang mengumpulkan hobi seluruh kenalan setiap orang.

cypher
MATCH (p:Person)-[:KENAL]->(lain:Person)-[:PUNYA_HOBI]->(h:Hobi)
WITH p, collect(DISTINCT h.name) AS hobi_kenalan
RETURN p.name AS nama, hobi_kenalan
ORDER BY nama;
text Keluaran
nama, hobi_kenalan
"Eko", ["Bersepeda"]
"Gita", ["Fotografi"]
"Iwan", ["Fotografi"]

Fajar tidak muncul karena MATCH menuntut rantai pola terpenuhi utuh, sedangkan kenalannya tidak memiliki hobi, sehingga kasus semacam itu ditangani OPTIONAL MATCH. Bentuk collect(DISTINCT ...) membuang nilai ganda apabila dua baris membawa nilai yang sama. Setelah terkumpul sebagai list, hasilnya siap diolah lebih lanjut pada tahap berikutnya.

Kesalahan umum#

Kesalahan pertama adalah menempatkan fungsi agregat langsung di dalam WHERE yang mengikuti MATCH, misalnya WHERE count(lain) >= 2. Eksekusinya berhenti dengan error invalid use of aggregate function, karena fungsi agregat menghitung atas banyak baris, sedangkan WHERE pada posisi itu dievaluasi baris demi baris. Perbaikannya adalah memindahkan agregasi ke WITH, lalu menyaring nilai agregat pada WHERE setelahnya.

Kesalahan kedua adalah menyaring pada posisi yang keliru. WHERE sebelum WITH menyaring baris pola, sedangkan WHERE sesudah WITH menyaring kelompok hasil agregasinya. Memindahkan kondisi antarposisi tanpa memeriksa maknanya mengubah angka yang dihitung, karena baris yang dikeluarkan sebelum agregasi tidak pernah ikut terhitung.

Kesalahan ketiga adalah menganggap collect otomatis membuang nilai ganda. Dua baris yang membawa nilai sama menghasilkan dua anggota list, misalnya ketika dua kenalan berbeda mempunyai hobi yang sama. Perbaikannya adalah menuliskan collect(DISTINCT x) apabila nilai unik yang dikehendaki, sebagaimana digunakan pada kueri hobi kenalan di bagian sebelumnya.

Ringkasan#

  • Fungsi agregat menggabungkan banyak baris menjadi satu nilai, dan setiap ekspresi non-agregat pada RETURN atau WITH menjadi kunci pengelompokan secara otomatis.
  • collect mengumpulkan nilai dari banyak baris menjadi satu list per kelompok, sedangkan collect(DISTINCT x) membuang nilai gandanya.
  • WITH meneruskan hasil ke bagian kueri berikutnya, melakukan agregasi di tengah kueri, dan WHERE sesudah WITH menyaring hasil agregasi tersebut.
  • Pola MATCH, WITH agregasi, WHERE, lalu MATCH atau RETURN menyusun kueri bertingkat sebagai pipeline yang setiap tahapnya mengubah baris.

Referensi#