Menyaring dan membentuk hasil

WHERE, DISTINCT, ORDER BY, LIMIT, dan proyeksi RETURN pada kueri Cypher.

Daftar isi

Hasil kueri Cypher jarang ditampilkan apa adanya. Baris yang tidak relevan perlu disaring, nilai ganda perlu dipilah, dan urutan baris perlu dikunci sebelum keluaran dikirim ke aplikasi. Topik ini membahas klausa WHERE untuk penyaringan baris, DISTINCT untuk pemilahan nilai ganda, ORDER BY dan LIMIT untuk pengurutan serta pembatasan, dan proyeksi RETURN untuk membentuk kolom keluaran melalui alias, ekspresi, dan peta nilai. Setiap contoh dijalankan pada dataset modul ini, sehingga keluarannya dapat direproduksi ulang.

Dataset contoh#

Seluruh contoh menggunakan dataset jaringan sosial yang sudah dimuat pada topik pola dalam MATCH, yaitu empat orang bernama Ali, Budi, Citra, dan Dewi, serta empat kota bernama Jakarta, Bandung, Surabaya, dan Medan. Setiap orang disimpan sebagai node berlabel :Person, setiap kota sebagai node berlabel :Kota, relasi :KENAL menyimpan tanggal perkenalan pada properti sejak, dan relasi :DOMISILI menghubungkan orang dengan kota tinggalnya. Topik ini menambahkan properti umur pada Ali, Citra, dan Dewi, sedangkan umur Budi sengaja tidak dicatat agar nilai null, yaitu penanda nilai yang tidak ada, ikut tersedia di dalam dataset. Nilai umur dituliskan secara statis, dan tahunLahir tetap disimpan karena digunakan pada bagian proyeksi RETURN.

Berkas setup.cypher berikut memuat dataset tersebut secara idempoten (idempotent), artinya aman dijalankan berulang kali karena data yang sudah ada tidak dibuat ulang. Jalankan berkasnya melalui cypher-shell sebelum mencoba kueri pada bagian berikutnya. Seluruh kueri pada topik ini hanya membaca data, sehingga tidak ada perubahan yang perlu dibatalkan.

cypher setup.cypher
// Dataset contoh modul Fundamental Cypher: jaringan sosial kecil
// dengan empat orang dan empat kota.
// Skrip ini idempoten (idempotent), artinya aman dijalankan berulang kali.

// Node orang: label :Person dengan properti name dan tahunLahir.
// Umur Budi sengaja tidak dicatat agar contoh nilai null tetap tersedia.
MERGE (ali:Person {name: "Ali"})
  SET ali.tahunLahir = 1990, ali.umur = 36;
MERGE (budi:Person {name: "Budi"})
  SET budi.tahunLahir = 1988;
MERGE (citra:Person {name: "Citra"})
  SET citra.tahunLahir = 1992, citra.umur = 34;
MERGE (dewi:Person {name: "Dewi"})
  SET dewi.tahunLahir = 1985, dewi.umur = 41;

// Node kota: label :Kota dengan properti name.
MERGE (jakarta:Kota {name: "Jakarta"});
MERGE (bandung:Kota {name: "Bandung"});
MERGE (surabaya:Kota {name: "Surabaya"});
MERGE (medan:Kota {name: "Medan"});

// Relasi KENAL berarah antar orang, dengan properti sejak.
MATCH (ali:Person {name: "Ali"}), (budi:Person {name: "Budi"})
  MERGE (ali)-[r:KENAL]->(budi)
  SET r.sejak = date("2019-03-10");
MATCH (ali:Person {name: "Ali"}), (citra:Person {name: "Citra"})
  MERGE (ali)-[r:KENAL]->(citra)
  SET r.sejak = date("2021-01-15");
MATCH (budi:Person {name: "Budi"}), (citra:Person {name: "Citra"})
  MERGE (budi)-[r:KENAL]->(citra)
  SET r.sejak = date("2020-07-21");
MATCH (citra:Person {name: "Citra"}), (dewi:Person {name: "Dewi"})
  MERGE (citra)-[r:KENAL]->(dewi)
  SET r.sejak = date("2018-11-05");
MATCH (dewi:Person {name: "Dewi"}), (ali:Person {name: "Ali"})
  MERGE (dewi)-[r:KENAL]->(ali)
  SET r.sejak = date("2017-06-02");

// Relasi DOMISILI dari orang menuju kota tempat tinggalnya.
MATCH (ali:Person {name: "Ali"}), (jakarta:Kota {name: "Jakarta"})
  MERGE (ali)-[:DOMISILI]->(jakarta);
MATCH (budi:Person {name: "Budi"}), (bandung:Kota {name: "Bandung"})
  MERGE (budi)-[:DOMISILI]->(bandung);
MATCH (citra:Person {name: "Citra"}), (bandung:Kota {name: "Bandung"})
  MERGE (citra)-[:DOMISILI]->(bandung);
MATCH (dewi:Person {name: "Dewi"}), (medan:Kota {name: "Medan"})
  MERGE (dewi)-[:DOMISILI]->(medan);
bash
# tidak-dijalankan
# memuat dataset contoh dari berkas setup.cypher ke dalam basis data
cat setup.cypher | cypher-shell -u neo4j

Menyaring baris dengan WHERE#

Klausa WHERE menyaring baris hasil MATCH dengan mengevaluasi sebuah predikat (predicate), yaitu ekspresi yang bernilai benar atau salah. Klausa ini ditulis setelah MATCH. Hanya baris pencocokan yang predikatnya bernilai benar yang dipertahankan. Operator perbandingannya mencakup =, <>, <, <=, >, dan >=, sedangkan rentang nilai ditulis sebagai dua perbandingan yang dirangkai AND, misalnya p.umur >= 30 AND p.umur <= 40. Kueri berikut mengambil orang yang umurnya dicatat minimal 35 tahun.

cypher
MATCH (p:Person)
WHERE p.umur >= 35
RETURN p.name AS nama, p.umur AS umur
ORDER BY nama;
text Keluaran
nama, umur
"Ali", 36
"Dewi", 41

Keluaran hanya memuat Ali dan Dewi. Baris Budi tidak muncul karena p.umur bernilai null, dan perbandingan apa pun dengan null menghasilkan null, bukan benar atau salah. Hanya predikat bernilai benar yang mempertahankan baris, sehingga properti yang tidak tercatat tersaring keluar tanpa syarat tambahan.

Syarat majemuk dirangkai dengan operator boolean AND, OR, dan NOT. Operator AND mengikat lebih kuat daripada OR, sehingga tanda kurung diperlukan apabila pengelompokannya berbeda, sedangkan NOT membalik nilai kebenaran syarat yang mengikutinya. Pencocokan string disediakan melalui STARTS WITH, ENDS WITH, dan CONTAINS untuk memeriksa awalan, akhiran, dan bagian dalam sebuah nilai string, dan ketiganya peka terhadap huruf besar-kecil. Kueri berikut mencari orang yang domisilinya diawali “Ba” atau memuat “rta”.

cypher
MATCH (p:Person)-[:DOMISILI]->(k:Kota)
WHERE k.name STARTS WITH "Ba" OR k.name CONTAINS "rta"
RETURN p.name AS nama, k.name AS kota
ORDER BY nama;
text Keluaran
nama, kota
"Ali", "Jakarta"
"Budi", "Bandung"
"Citra", "Bandung"

Predikat juga boleh ditulis di dalam pola node, misalnya (p:Person WHERE p.umur IS NOT NULL). Predikat IS NOT NULL bernilai benar hanya untuk node yang propertinya benar-benar ada. Bentuk ini menyatukan pola dan syarat, sehingga kueri berikut terbaca sebagai pencarian orang yang umurnya tercatat beserta orang yang dikenalinya.

cypher
MATCH (p:Person WHERE p.umur IS NOT NULL)-[:KENAL]->(teman)
RETURN p.name AS nama, teman.name AS dikenal
ORDER BY nama, dikenal;
text Keluaran
nama, dikenal
"Ali", "Budi"
"Ali", "Citra"
"Citra", "Dewi"
"Dewi", "Ali"

Memilah nilai ganda dengan DISTINCT#

Klausa RETURN memetakan setiap baris pencocokan menjadi satu baris keluaran, sehingga nilai yang sama dapat muncul berkali-kali. Pengulangan semacam ini wajar, tetapi tidak selalu diinginkan pada keluaran akhir. Dua orang di dalam dataset tinggal di Bandung, karena relasi :DOMISILI dari Budi dan Citra mengarah ke kota yang sama. Kueri berikut memperlihatkan pengulangan itu.

cypher
MATCH (p:Person)-[:DOMISILI]->(k:Kota)
RETURN k.name AS kota
ORDER BY kota;
text Keluaran
kota
"Bandung"
"Bandung"
"Jakarta"
"Medan"

Kata DISTINCT ditulis sebelum daftar proyeksi dan membuang baris duplikat, yaitu baris yang seluruh nilainya sama dengan baris lain yang sudah dikembalikan. Keunikannya dinilai atas kombinasi seluruh kolom dalam satu baris, bukan kolom per kolom, sehingga RETURN DISTINCT p.name, k.name tetap dapat mengembalikan nama yang sama apabila kota-kotanya berbeda. Kueri yang sama dengan DISTINCT menyisakan tiga kota.

cypher
MATCH (p:Person)-[:DOMISILI]->(k:Kota)
RETURN DISTINCT k.name AS kota
ORDER BY kota;
text Keluaran
kota
"Bandung"
"Jakarta"
"Medan"

Pemilahan seperti ini lazim sebelum hasil digunakan lebih lanjut, misalnya untuk mengisi daftar pilihan pada aplikasi yang tidak memerlukan nilai ganda.

Mengurutkan dan membatasi dengan ORDER BY dan LIMIT#

Tanpa ORDER BY, urutan baris keluaran tidak dijamin. Klausa ORDER BY ditulis setelah RETURN dan mengurutkan baris menurut satu atau beberapa ekspresi yang dipisahkan koma, misalnya ORDER BY nama, umur yang mengurutkan menurut nama lebih dahulu. Urutan bawaannya menaik, dan kata DESC mengubahnya menjadi menurun. Ekspresi yang diurutkan boleh berupa kolom proyeksi maupun properti yang tidak ikut dikembalikan. Kueri berikut mengurutkan orang menurut umurnya.

cypher
MATCH (p:Person)-[:DOMISILI]->(k:Kota)
RETURN p.name AS nama, p.umur AS umur
ORDER BY umur;
text Keluaran
nama, umur
"Citra", 34
"Ali", 36
"Dewi", 41
"Budi", NULL

Baris Budi menempati posisi terakhir karena p.umur bernilai null. Cypher menempatkan null di akhir urutan menaik dan di awal urutan menurun, sehingga posisinya berpindah ketika arah pengurutan dibalik. Apabila posisi null mengganggu pembacaan, saring barisnya terlebih dahulu dengan IS NOT NULL.

cypher
MATCH (p:Person)-[:DOMISILI]->(k:Kota)
RETURN p.name AS nama, p.umur AS umur
ORDER BY umur DESC;
text Keluaran
nama, umur
"Budi", NULL
"Dewi", 41
"Ali", 36
"Citra", 34

Klausa LIMIT ditulis setelah ORDER BY dan menyisakan sejumlah baris pertama saja. Rangkaian menyaring dengan WHERE, mengurutkan dengan ORDER BY, lalu membatasi dengan LIMIT membentuk alur kerja yang lazim pada aplikasi. Kombinasi ini juga membuat keluaran menjadi pasti, karena pembatasan bekerja di atas urutan yang sudah dikunci. Kueri berikut mengambil dua orang tertua yang umurnya tercatat.

cypher
MATCH (p:Person)
WHERE p.umur IS NOT NULL
RETURN p.name AS nama, p.umur AS umur
ORDER BY umur DESC
LIMIT 2;
text Keluaran
nama, umur
"Dewi", 41
"Ali", 36

Membentuk hasil dengan proyeksi RETURN#

Bentuk keluaran ditentukan oleh proyeksi pada klausa RETURN. Setiap item proyeksi dapat diberi alias melalui kata AS, yaitu nama kolom baru yang tampil pada header keluaran. Alias juga memberi nama pada hasil sebuah ekspresi, sebagaimana kueri berikut yang menghitung perkiraan umur dari tahunLahir.

cypher
MATCH (p:Person)-[:DOMISILI]->(k:Kota)
RETURN p.name AS nama, k.name AS kota, 2026 - p.tahunLahir AS perkiraanUmur
ORDER BY nama;
text Keluaran
nama, kota, perkiraanUmur
"Ali", "Jakarta", 36
"Budi", "Bandung", 38
"Citra", "Bandung", 34
"Dewi", "Medan", 41

Kolom perkiraanUmur dihasilkan ekspresi aritmetika 2026 - p.tahunLahir, dan angka 2026 ditulis langsung agar keluaran tetap sama pada setiap eksekusi. Nilai yang dihitung itu cocok dengan properti umur untuk tiga orang, sedangkan Budi yang umurnya tidak dicatat tetap memperoleh angka dari tahun lahirnya.

Bentuk proyeksi ketiga adalah peta nilai (map projection), yaitu objek berkurung kurawal yang dibangun dari sebuah variabel. Notasi .name menyertakan properti name apa adanya, sedangkan kunci: ekspresi membuat kunci baru dari sebuah ekspresi. Kueri berikut merangkum setiap orang beserta kota domisilinya menjadi satu nilai peta.

cypher
MATCH (p:Person)-[:DOMISILI]->(k:Kota)
RETURN p { .name, .umur, domisili: k.name } AS profil
ORDER BY p.name;
text Keluaran
profil
{name: "Ali", domisili: "Jakarta", umur: 36}
{name: "Budi", domisili: "Bandung", umur: NULL}
{name: "Citra", domisili: "Bandung", umur: 34}
{name: "Dewi", domisili: "Medan", umur: 41}

Satu kolom peta menghimpun beberapa fakta sekaligus, sehingga praktis dijadikan respons API. Kunci di dalam peta tidak menyimpan urutan, sehingga urutan tampil pada keluaran dapat berbeda dari urutan penulisannya. Properti yang tidak ada menghasilkan kunci bernilai null, sebagaimana umur pada peta Budi.

Kesalahan umum#

  1. Memperlakukan perbandingan dengan null sebagai salah. Predikat p.umur > 30 pada baris yang umurnya null bernilai null, bukan salah, dan NOT atas null juga tetap null, sehingga baris itu tersaring keluar dari kueri mana pun. Keluaran yang kosong padahal data seharusnya ada biasanya berasal dari perilaku ini. Tangani kondisi properti yang tidak ada secara eksplisit dengan IS NOT NULL sebelum perbandingannya, sedangkan IS NULL berlaku sebaliknya, yaitu bernilai benar hanya untuk properti yang tidak ada.
  2. Menulis LIMIT tanpa ORDER BY. Urutan baris tanpa pengurutan tidak dijamin, sehingga baris yang tersisa dapat berbeda antar eksekusi. LIMIT 5 tanpa pengurutan bukan lima baris terbaik, melainkan lima baris apa pun. Pasangkan LIMIT dengan ORDER BY yang menegaskan makna baris teratasnya.
  3. Mengira DISTINCT menyatukan node. Proyeksi RETURN DISTINCT p tetap mengembalikan satu baris untuk setiap node karena dua node berbeda dianggap dua nilai yang berbeda. Pilah propertinya sebagai gantinya, misalnya RETURN DISTINCT p.name.

Ringkasan#

  • WHERE menyaring baris pencocokan dengan predikat, perbandingan yang melibatkan null menghasilkan null sehingga barisnya tersaring keluar, dan IS NULL atau IS NOT NULL menangani kondisi tersebut secara eksplisit.
  • DISTINCT membuang baris yang kombinasi nilai seluruh kolomnya sama dengan baris lain.
  • ORDER BY mengunci urutan baris, menaik secara bawaan atau menurun melalui DESC, dengan null di akhir urutan menaik dan di awal urutan menurun, sedangkan LIMIT menyisakan sejumlah baris pertama.
  • Proyeksi RETURN membentuk kolom keluaran melalui alias AS, ekspresi aritmetika, dan peta nilai yang menghimpun beberapa properti dalam satu kolom.

Referensi#