Daftar buku termurah lebih dulu, buku terbaru paling atas, kategori dikelompokkan lalu diurutkan per harga — hampir semua layar aplikasi menuntut urutan tertentu, sementara find() tanpa pengurutan mengembalikan document dalam urutan penyimpanan yang tidak boleh kita andalkan. Method sort() pada cursor mengatur urutan itu di sisi server sebelum hasil dikirim. Chapter ini membahas pengurutan satu field, multi-field, perilaku khusus pada nilai null dan field yang hilang, sampai urutan antar tipe BSON yang sering mengejutkan pemula.
Peta materi Chapter ini:
sort()satu field dan kombinasi multi-field.- Urutan bawaan antar tipe BSON (null, number, string, object, dan seterusnya).
- Batas memori blocking sort dan kerja sama
sort()dengan index.
Mengurutkan Satu Field #
Siapkan katalog datar untuk chapter ini:
use latihan_query
db.buku.drop()Output yang Dihasilkan:
switched to db latihan_query
truedb.buku.insertMany([
{ judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000, stok: 12, kategori: "fiksi", rating: 4.7 },
{ judul: "Bumi", penulis: "Tere Liye", harga: 89000, stok: 25, kategori: "fiksi", rating: 4.5 },
{ judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 89000, stok: 18, kategori: "nonfiksi", rating: 4.8 },
{ judul: "Atomic Habits", penulis: "James Clear", harga: 115000, stok: 30, kategori: "nonfiksi", rating: 4.6 },
{ judul: "Rumah Kaca", penulis: "Tere Liye", harga: 72000, stok: 9, kategori: "fiksi", rating: 4.3 },
{ judul: "Kamus Bahasa Indonesia", penulis: "Badan Bahasa", harga: 55000, stok: 4, kategori: "referensi" }
])Output yang Dihasilkan:
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad38f839ab94d1b77be5a90'),
'1': ObjectId('6ad38f839ab94d1b77be5a91'),
'2': ObjectId('6ad38f839ab94d1b77be5a92'),
'3': ObjectId('6ad38f839ab94d1b77be5a93'),
'4': ObjectId('6ad38f839ab94d1b77be5a94'),
'5': ObjectId('6ad38f839ab94d1b77be5a95')
}
}sort() menerima object yang memetakan field ke arah urutan: 1 menaik, -1 menurun. Buku termurah lebih dulu:
db.buku.find({}, { _id: 0, judul: 1, harga: 1 }).sort({ harga: 1 })Output yang Dihasilkan:
[
{ judul: 'Kamus Bahasa Indonesia', harga: 55000 },
{ judul: 'Rumah Kaca', harga: 72000 },
{ judul: 'Bumi', harga: 89000 },
{ judul: 'Filosofi Teras', harga: 89000 },
{ judul: 'Laut Bercerita', harga: 98000 },
{ judul: 'Atomic Habits', harga: 115000 }
]Arah sebaliknya menempatkan buku termahal di atas:
db.buku.find({}, { _id: 0, judul: 1, harga: 1 }).sort({ harga: -1 })Output yang Dihasilkan:
[
{ judul: 'Atomic Habits', harga: 115000 },
{ judul: 'Laut Bercerita', harga: 98000 },
{ judul: 'Bumi', harga: 89000 },
{ judul: 'Filosofi Teras', harga: 89000 },
{ judul: 'Rumah Kaca', harga: 72000 },
{ judul: 'Kamus Bahasa Indonesia', harga: 55000 }
]Perhatikan Bumi dan Filosofi Teras yang sama-sama berharga 89000: urutan keduanya tidak ditentukan oleh sort({ harga: 1 }) dan bisa berbeda antar eksekusi. Nilai yang sama (tie) butuh field berikutnya sebagai pemecah.
Multi-Field dan Tie-Breaker #
Object sort boleh memuat lebih dari satu field; server mengurutkan berdasarkan field pertama, lalu field kedua untuk nilai yang sama, dan seterusnya. Kelompokkan per kategori, lalu dalam tiap kelompok urutkan harga dari yang termahal:
db.buku.find({}, { _id: 0, judul: 1, kategori: 1, harga: 1 }).sort({ kategori: 1, harga: -1 })Output yang Dihasilkan:
[
{ judul: 'Laut Bercerita', harga: 98000, kategori: 'fiksi' },
{ judul: 'Bumi', harga: 89000, kategori: 'fiksi' },
{ judul: 'Rumah Kaca', harga: 72000, kategori: 'fiksi' },
{ judul: 'Atomic Habits', harga: 115000, kategori: 'nonfiksi' },
{ judul: 'Filosofi Teras', harga: 89000, kategori: 'nonfiksi' },
{
judul: 'Kamus Bahasa Indonesia',
harga: 55000,
kategori: 'referensi'
}
]Hasilnya: tiga buku fiksi berurutan dari 98000 turun ke 72000, disusul dua nonfiksi, terakhir referensi. Untuk tie harga 89000 tadi, _id adalah tie-breaker yang stabil karena nilainya unik:
db.buku.find({}, { _id: 0, judul: 1 }).sort({ harga: 1, _id: 1 })Output yang Dihasilkan:
[
{ judul: 'Kamus Bahasa Indonesia' },
{ judul: 'Rumah Kaca' },
{ judul: 'Bumi' },
{ judul: 'Filosofi Teras' },
{ judul: 'Laut Bercerita' },
{ judul: 'Atomic Habits' }
]Mengurutkan _id menurun punya satu kegunaan populer: mengambil document yang paling baru disisipkan, karena ObjectId mengandung timestamp yang selalu bertambah:
db.buku.find({}, { judul: 1, _id: 0 }).sort({ _id: -1 }).limit(2)Output yang Dihasilkan:
[ { judul: 'Kamus Bahasa Indonesia' }, { judul: 'Rumah Kaca' } ]Dua buku terakhir yang disisipkan — Kamus Bahasa Indonesia dan Atomic Habits — tampil paling awal.
Urutan Antar Tipe BSON #
MongoDB bersifat schemaless: satu field boleh berisi tipe apa pun di document yang berbeda. Saat field yang diurutkan bertipe campuran, server memakai urutan tetap antar tipe BSON ini:
| Urutan | Tipe |
|---|---|
| 1 | null |
| 2 | angka (number) |
| 3 | string |
| 4 | object |
| 5 | array |
| 6 | binData |
| 7 | objectId |
| 8 | boolean |
| 9 | date |
| 10 | timestamp |
| 11 | regex |
Uji urutan itu dengan collection kecil berisi satu field x yang sengaja diisi tipe berbeda-beda:
db.campuran.drop()Output yang Dihasilkan:
truedb.campuran.insertMany([
{ label: "boolean", x: true },
{ label: "date", x: ISODate("2024-05-01T00:00:00Z") },
{ label: "array", x: [1, 2] },
{ label: "objectId", x: ObjectId("507f1f77bcf86cd799439011") },
{ label: "string", x: "teks" },
{ label: "regex", x: /pola/ },
{ label: "null", x: null },
{ label: "object", x: { a: 1 } },
{ label: "number", x: 42 },
{ label: "timestamp", x: Timestamp({ t: 100, i: 1 }) },
{ label: "binData", x: BinData(0, "aGVsbG8=") }
])Output yang Dihasilkan:
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad38f849ab94d1b77be5a96'),
'1': ObjectId('6ad38f849ab94d1b77be5a97'),
'2': ObjectId('6ad38f849ab94d1b77be5a98'),
'3': ObjectId('6ad38f849ab94d1b77be5a99'),
'4': ObjectId('6ad38f849ab94d1b77be5a9a'),
'5': ObjectId('6ad38f849ab94d1b77be5a9b'),
'6': ObjectId('6ad38f849ab94d1b77be5a9c'),
'7': ObjectId('6ad38f849ab94d1b77be5a9d'),
'8': ObjectId('6ad38f849ab94d1b77be5a9e'),
'9': ObjectId('6ad38f849ab94d1b77be5a9f'),
'10': ObjectId('6ad38f849ab94d1b77be5aa0')
}
}db.campuran.find({}, { _id: 0 }).sort({ x: 1 })Output yang Dihasilkan:
[
{ label: 'null', x: null },
{ label: 'array', x: [ 1, 2 ] },
{ label: 'number', x: 42 },
{ label: 'string', x: 'teks' },
{ label: 'object', x: { a: 1 } },
{ label: 'binData', x: Binary.createFromBase64('aGVsbG8=', 0) },
{ label: 'objectId', x: ObjectId('507f1f77bcf86cd799439011') },
{ label: 'boolean', x: true },
{ label: 'date', x: ISODate('2024-05-01T00:00:00.000Z') },
{ label: 'timestamp', x: Timestamp({ t: 100, i: 1 }) },
{ label: 'regex', x: /pola/ }
]Urutan hasilnya mengikuti tabel: null paling depan, disusul array dan angka, string, object, binData, ObjectId, boolean, date, timestamp, dan regex paling belakang. Satu hal perlu dibedah: array [1, 2] muncul sebelum angka 42, bukan di posisi kelima sesuai tabel. Saat mengurutkan field array secara menaik, MongoDB membandingkan array memakai elemen terkecilnya — [1, 2] diwakili angka 1, sehingga berada di kelompok angka sebelum 42. Pada urutan menurun, aturannya dibalik: array diwakili elemen terbesarnya.
db.campuran.find({}, { _id: 0 }).sort({ x: -1 })Output yang Dihasilkan:
[
{ label: 'regex', x: /pola/ },
{ label: 'timestamp', x: Timestamp({ t: 100, i: 1 }) },
{ label: 'date', x: ISODate('2024-05-01T00:00:00.000Z') },
{ label: 'boolean', x: true },
{ label: 'objectId', x: ObjectId('507f1f77bcf86cd799439011') },
{ label: 'binData', x: Binary.createFromBase64('aGVsbG8=', 0) },
{ label: 'object', x: { a: 1 } },
{ label: 'string', x: 'teks' },
{ label: 'number', x: 42 },
{ label: 'array', x: [ 1, 2 ] },
{ label: 'null', x: null }
]Perhatikan array [1, 2] kini jatuh setelah angka 42 — elemen terbesarnya, 2, lebih kecil dari 42. Aturan elemen terkecil/terbesar ini penting saat field array ikut diurutkan bersama field lain.
Field yang tidak ada juga punya tempat: document tanpa field yang diurutkan diperlakukan seolah field itu bernilai null, sehingga berada paling depan pada urutan menaik. Kamus Bahasa Indonesia di katalog kita tidak memiliki rating:
db.buku.find({}, { _id: 0, judul: 1, rating: 1 }).sort({ rating: 1 })Output yang Dihasilkan:
[
{ judul: 'Kamus Bahasa Indonesia' },
{ judul: 'Rumah Kaca', rating: 4.3 },
{ judul: 'Bumi', rating: 4.5 },
{ judul: 'Atomic Habits', rating: 4.6 },
{ judul: 'Laut Bercerita', rating: 4.7 },
{ judul: 'Filosofi Teras', rating: 4.8 }
]Kamus Bahasa Indonesia muncul pertama tanpa field rating sama sekali — persis seperti document bernilai null.
Batas Memori dan Kerja Sama Index #
Tanpa index yang cocok, server harus mengumpulkan seluruh hasil ke memori untuk mengurutkannya — operasi yang disebut blocking sort. Operasi ini dibatasi parameter internal internalQueryMaxBlockingSortMemoryUsageBytes sebesar 100MB pada MongoDB 9.0 (pada versi sebelum 6.0 batasnya 32MB). Melebihi batas itu, query gagal dengan pesan Sort exceeded memory limit; jalan keluarnya adalah cursor.allowDiskUse() untuk memindahkan pengurutan ke disk, atau — jauh lebih baik — index pada field sort.
Satu catatan penutup tentang bahasa: perbandingan string di atas memakai urutan biner BSON, bukan aturan alfabet bahasa tertentu — huruf besar mendahului huruf kecil, dan “é” tidak bersebelahan dengan “e”. MongoDB menyediakan collation untuk aturan urutan per bahasa, tetapi pembahasannya berada di luar cakupan course dasar ini.
Rangkuman #
sort({ field: 1 })menaik,sort({ field: -1 })menurun; beberapa field diurutkan berurutan dari kiri.- Nilai yang sama butuh tie-breaker (misalnya
_id);sort({ _id: -1 })mengambil document terbaru. - Tipe BSON terurut tetap: null, angka, string, object, array, binData, objectId, boolean, date, timestamp, regex; field yang hilang dihitung
null. - Field array dibandingkan lewat elemen terkecilnya (menaik) atau terbesarnya (menurun).
- Blocking sort di memori dibatasi 100MB (sebelumnya 32MB); index pada field sort adalah solusi permanennya.