$sort, $limit, dan $count

Mengurutkan, membatasi, dan menghitung dokumen di dalam pipeline dengan $sort, $limit, dan $count.

Daftar isi

Tahap $sort mengurutkan aliran dokumen, tahap $limit membatasi jumlahnya, dan tahap $count menghitung banyaknya dokumen yang lolos dari tahap sebelumnya. Ketiganya sering muncul bersama pada laporan, misalnya daftar peringkat teratas beserta jumlah barisnya. Topik ini membahas cara kerja masing-masing tahap beserta pola gabungannya. Data contohnya berupa koleksi siswa pada basis data sekolah, dan setiap pipeline dijalankan di dalam sesi mongosh.

Mengurutkan dokumen dengan $sort#

Tahap $sort mengurutkan dokumen berdasarkan satu atau beberapa field, dengan nilai 1 untuk urutan menaik (ascending) dan -1 untuk urutan menurun (descending). Argumennya berupa satu dokumen yang kuncinya adalah nama field dan nilainya adalah arah urutan. Pipeline berikut mengurutkan siswa menurut namanya secara menaik, lalu menampilkan hanya nama dan kelasnya.

Jalankan mongosh, pilih basis data kerja, lalu isi koleksi siswa dengan delapan dokumen berikut.

mongosh
use sekolah
text Keluaran
switched to db sekolah
mongosh
db.siswa.insertMany([
  { nama: "Ali Rahman", kelas: "8A", umur: 14, hobi: ["catur", "futsal"] },
  { nama: "Budi Santoso", kelas: "8A", umur: 13, hobi: ["futsal"] },
  { nama: "Citra Melati", kelas: "8A", umur: 14, hobi: ["seni tari", "catur"] },
  { nama: "Dewi Lestari", kelas: "8B", umur: 13, hobi: ["vokal"] },
  { nama: "Eka Pratama", kelas: "8B", umur: 14, hobi: ["futsal", "renang"] },
  { nama: "Fitri Handayani", kelas: "8B", umur: 15, hobi: ["seni tari"] },
  { nama: "Gilang Saputra", kelas: "8C", umur: 14, hobi: ["renang", "catur", "futsal"] },
  { nama: "Hana Puspita", kelas: "8C", umur: 13, hobi: ["vokal", "seni tari"] }
])
text Keluaran
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ae8937602e8261bf08de666'),
    '1': ObjectId('6ae8937602e8261bf08de667'),
    '2': ObjectId('6ae8937602e8261bf08de668'),
    '3': ObjectId('6ae8937602e8261bf08de669'),
    '4': ObjectId('6ae8937602e8261bf08de66a'),
    '5': ObjectId('6ae8937602e8261bf08de66b'),
    '6': ObjectId('6ae8937602e8261bf08de66c'),
    '7': ObjectId('6ae8937602e8261bf08de66d')
  }
}
mongosh
db.siswa.aggregate([
  { $sort: { nama: 1 } },
  { $project: { _id: 0, nama: 1, kelas: 1 } }
])
text Keluaran
[
  { nama: 'Ali Rahman', kelas: '8A' },
  { nama: 'Budi Santoso', kelas: '8A' },
  { nama: 'Citra Melati', kelas: '8A' },
  { nama: 'Dewi Lestari', kelas: '8B' },
  { nama: 'Eka Pratama', kelas: '8B' },
  { nama: 'Fitri Handayani', kelas: '8B' },
  { nama: 'Gilang Saputra', kelas: '8C' },
  { nama: 'Hana Puspita', kelas: '8C' }
]

Hasilnya delapan dokumen yang tersusun menurut abjad nama, yaitu dari Ali Rahman hingga Hana Puspita. Pembanding teks mengikuti urutan biner string, yaitu urutan menurut kode karakter, sehingga huruf kapital dan kecil diperlakukan berbeda. Karena nilai nama pada data contoh semuanya berbeda, satu kunci urutan sudah cukup untuk menghasilkan susunan yang pasti.

Field yang tidak dimiliki sebuah dokumen diperlakukan seperti bernilai null pada pembandingan. Dokumen tanpa field kunci urutan terkumpul di awal keluaran menaik dan di akhir keluaran menurun. Data contoh tidak memuat kasus tersebut karena seluruh siswanya memiliki field yang dirujuk. Meskipun demikian, posisi dokumen tanpa field tetap perlu diperhitungkan apabila koleksi nyata memuat dokumen yang strukturnya tidak seragam.

Mengurutkan dengan beberapa kunci#

Apabila beberapa dokumen memiliki nilai yang sama pada kunci urutannya, urutan di antara dokumen tersebut tidak dijamin oleh server. Kondisi ini disebut nilai seri (tie), dan penanganannya adalah menambahkan kunci kedua sebagai pemecahnya. Pipeline berikut mengurutkan siswa dari yang tertua, lalu memecah nilai seri dokumen yang seumur dengan nama secara abjad.

mongosh
db.siswa.aggregate([
  { $sort: { umur: -1, nama: 1 } },
  { $project: { _id: 0, nama: 1, umur: 1 } }
])
text Keluaran
[
  { nama: 'Fitri Handayani', umur: 15 },
  { nama: 'Ali Rahman', umur: 14 },
  { nama: 'Citra Melati', umur: 14 },
  { nama: 'Eka Pratama', umur: 14 },
  { nama: 'Gilang Saputra', umur: 14 },
  { nama: 'Budi Santoso', umur: 13 },
  { nama: 'Dewi Lestari', umur: 13 },
  { nama: 'Hana Puspita', umur: 13 }
]

Fitri Handayani berumur 15 tahun menempati urutan pertama, sedangkan empat siswa berumur 14 tahun tersusun menurut abjad karena kunci kedua. Tanpa kunci nama, urutan di antara keempatnya boleh berubah antareksekusi. Data yang sama selalu menghasilkan urutan yang sama apabila kombinasi seluruh kunci urutan bersifat unik pada setiap dokumen.

Membatasi dokumen dengan $limit#

Tahap $limit meneruskan sejumlah dokumen pertama dari aliran masukannya dan membuang sisanya. Tahap ini hampir selalu dipasang setelah $sort untuk membentuk pola peringkat teratas, karena mengurutkan lebih dahulu menjamin dokumen yang diambil memang yang paling cocok dengan kriterianya.

mongosh
db.siswa.aggregate([
  { $sort: { umur: -1, nama: 1 } },
  { $limit: 3 },
  { $project: { _id: 0, nama: 1, umur: 1 } }
])
text Keluaran
[
  { nama: 'Fitri Handayani', umur: 15 },
  { nama: 'Ali Rahman', umur: 14 },
  { nama: 'Citra Melati', umur: 14 }
]

Keluarannya tiga siswa tertua, yaitu satu siswa berumur 15 tahun dan dua siswa berumur 14 tahun dengan nama terawal. Urutan tahap pada pola ini bersifat tetap. Apabila $limit diletakkan sebelum $sort, tahap itu hanya mengurangi bahan yang akan diurutkan dan hasilnya berbeda dari peringkat yang diinginkan. Pola $sort diikuti $limit juga memanfaatkan indeks dengan baik apabila kunci urutannya terindeks.

Tahap ini tidak mengubah isi dokumen yang diteruskan, melainkan hanya mengurangi jumlahnya. Angka pada $limit wajib berupa bilangan bulat positif, dan nilai 0 ditolak server dengan pesan error the limit must be positive. Apabila jumlah dokumen masukannya lebih sedikit daripada batas tersebut, seluruh dokumen diteruskan apa adanya.

Menghitung dokumen dengan $count#

Tahap $count mengganti seluruh aliran masukannya dengan satu dokumen yang memuat jumlahnya. Argumennya berupa teks nama field untuk menyimpan angka hasil hitung, misalnya jumlahSiswa. Pipeline berikut menghitung seluruh siswa, lalu pipeline kedua menghitung siswa yang hobinya memuat renang.

mongosh
db.siswa.aggregate([
  { $count: "jumlahSiswa" }
])
text Keluaran
[ { jumlahSiswa: 8 } ]
mongosh
db.siswa.aggregate([
  { $match: { hobi: "renang" } },
  { $count: "jumlahSiswa" }
])
text Keluaran
[ { jumlahSiswa: 2 } ]

Pipeline pertama menghasilkan 8 karena seluruh dokumen koleksi masuk ke hitungannya, sedangkan pipeline kedua menghasilkan 2 karena $match menyaringnya lebih dahulu. Pemakaian $count setelah $match inilah bentuk paling lazimnya, yaitu menghitung baris yang memenuhi syarat tertentu.

Padanannya pada SQL adalah SELECT COUNT(*) dengan klausa WHERE sesuai $match yang mendahuluinya. Tahap $count sebenarnya kasus khusus dari $group, yaitu pengelompokan seluruh masukan dengan akumulator $sum bernilai 1. Hasil yang sama dapat dibentuk melalui $group apabila ringkasan lain ikut dibutuhkan. Untuk kebutuhan yang murni jumlah baris, $count tetap lebih ringkas untuk ditulis.

Perilaku $count pada masukan kosong perlu diperhatikan secara khusus. Apabila tidak ada dokumen yang lolos dari tahap sebelumnya, $count tidak menghasilkan dokumen apa pun, bukan dokumen bernilai nol. Pembungkus .toArray() dipakai berikut agar kondisi tersebut terlihat sebagai array kosong.

mongosh
db.siswa.aggregate([
  { $match: { hobi: "basket" } },
  { $count: "jumlahSiswa" }
]).toArray()
text Keluaran
[]

Hasilnya array kosong karena tidak ada siswa yang hobinya basket. Kode pembaca yang mengharapkan dokumen berisi angka nol akan gagal pada kasus ini, sehingga hasil kursor perlu diperiksa keberadaannya lebih dahulu.

Ringkasan#

  • $sort mengurutkan dokumen dengan arah 1 menaik dan -1 menurun, serta menerima beberapa kunci sekaligus.
  • Nilai seri pada kunci urutan harus dipecah oleh kunci kedua agar urutan hasilnya pasti.
  • $limit meneruskan sejumlah dokumen pertama dan lazim dipasang setelah $sort untuk pola peringkat teratas.
  • $count mengganti aliran dokumennya dengan satu dokumen jumlah, dan tidak menghasilkan apa pun apabila masukannya kosong.

Referensi#