$bucket dan $bucketAuto

Membuat histogram dan analisis distribusi otomatis dengan $bucket dan $bucketAuto.

Daftar isi

Tahap $bucket dan $bucketAuto mengelompokkan dokumen ke dalam interval nilai alih-alih nilai kunci yang sama persis. Pembagian semacam ini membentuk histogram, misalnya banyaknya pegawai pada rentang gaji tertentu. $group biasa tidak dapat membentuk histogram semacam itu karena hanya menyatukan nilai yang identik. Topik ini membahas $bucket dengan batas interval yang ditentukan sendiri, opsi default untuk nilai di luar rentang, serta $bucketAuto yang menentukan batas intervalnya secara otomatis. Data contohnya berupa koleksi pegawai pada basis data pegawai yang memuat gaji sepuluh pegawai.

Membuat histogram dengan batas tetap#

Tahap $bucket menerima kunci groupBy berupa ekspresi nilai yang dikelompokkan dan kunci boundaries berupa daftar batas yang terurut naik. Setiap interval mencakup batas bawahnya dan tidak mencakup batas atasnya, sehingga nilai tepat sama dengan batas atas masuk ke interval berikutnya. Keluaran $bucket berupa satu dokumen untuk setiap interval terisi, dengan _id berupa batas bawahnya. Dokumen-dokumen tersebut terurut menaik menurut _id. Field output bersifat opsional dan berisi akumulator tambahan sebagaimana pada $group.

Jalankan mongosh, pilih basis data kerja, lalu isi koleksi pegawai dengan sepuluh dokumen berikut. Gaji dipilih menaik agar distribusinya mudah diperiksa, sedangkan urutan nama sengaja tidak alfabetis.

mongosh
use pegawai
text Keluaran
switched to db pegawai
mongosh
db.pegawai.insertMany([
  { nama: "Dewi Lestari", gaji: 4500000 },
  { nama: "Joko Prasetyo", gaji: 4900000 },
  { nama: "Hana Puspita", gaji: 5100000 },
  { nama: "Budi Santoso", gaji: 5600000 },
  { nama: "Fitri Handayani", gaji: 6200000 },
  { nama: "Citra Melati", gaji: 7300000 },
  { nama: "Indra Gunawan", gaji: 8700000 },
  { nama: "Eka Pratama", gaji: 9100000 },
  { nama: "Gilang Saputra", gaji: 10400000 },
  { nama: "Ali Rahman", gaji: 11500000 }
])
text Keluaran
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ae8990238e7eda4528de666'),
    '1': ObjectId('6ae8990238e7eda4528de667'),
    '2': ObjectId('6ae8990238e7eda4528de668'),
    '3': ObjectId('6ae8990238e7eda4528de669'),
    '4': ObjectId('6ae8990238e7eda4528de66a'),
    '5': ObjectId('6ae8990238e7eda4528de66b'),
    '6': ObjectId('6ae8990238e7eda4528de66c'),
    '7': ObjectId('6ae8990238e7eda4528de66d'),
    '8': ObjectId('6ae8990238e7eda4528de66e'),
    '9': ObjectId('6ae8990238e7eda4528de66f')
  }
}

Pipeline berikut membagi rentang gaji empat juta sampai dua belas juta menjadi empat interval selebar dua juta, lalu menghitung jumlah pegawai dan mengumpulkan namanya pada setiap interval.

mongosh
db.pegawai.aggregate([
  { $bucket: {
      groupBy: "$gaji",
      boundaries: [4000000, 6000000, 8000000, 10000000, 12000000],
      output: {
        jumlah: { $sum: 1 },
        daftarNama: { $push: "$nama" }
      }
  } }
])
text Keluaran
[
  {
    _id: 4000000,
    jumlah: 4,
    daftarNama: [ 'Dewi Lestari', 'Joko Prasetyo', 'Hana Puspita', 'Budi Santoso' ]
  },
  {
    _id: 6000000,
    jumlah: 2,
    daftarNama: [ 'Fitri Handayani', 'Citra Melati' ]
  },
  {
    _id: 8000000,
    jumlah: 2,
    daftarNama: [ 'Indra Gunawan', 'Eka Pratama' ]
  },
  {
    _id: 10000000,
    jumlah: 2,
    daftarNama: [ 'Gilang Saputra', 'Ali Rahman' ]
  }
]

Keluarannya empat dokumen sesuai banyaknya interval terisi, yaitu empat pegawai pada rentang empat sampai enam juta, dua pegawai pada setiap rentang berikutnya, dan totalnya sepuluh pegawai. Nama di dalam setiap daftarNama tampil terurut menurut gaji karena dokumen diproses menurut urutan nilai groupBy-nya. Nilai tepat 6000000 tidak ada pada data contoh, tetapi apabila ada, nilai itu akan masuk ke interval berikutnya dan bukan ke interval yang berakhir pada 6000000. Keluaran $bucket sudah terurut menurut _id, sehingga $sort tidak diperlukan setelahnya. Urutan nama di dalam array dapat diatur ulang pada lapisan aplikasi bila dibutuhkan.

Menampung nilai di luar rentang dengan default#

Opsi default menampung seluruh dokumen yang nilainya jatuh di luar rentang boundaries, baik di bawah batas pertama maupun di atas batas terakhir. Nilainya bebas ditentukan, dan dokumen penampungnya ikut terurut menurut _id, sehingga nilai default bertipe teks tampil setelah seluruh interval bertipe angka. Pipeline berikut hanya mendefinisikan dua interval tengah, sehingga gaji di luar keduanya berkumpul pada penampung.

mongosh
db.pegawai.aggregate([
  { $bucket: {
      groupBy: "$gaji",
      boundaries: [6000000, 8000000, 10000000],
      default: "di luar rentang",
      output: { jumlah: { $sum: 1 } }
  } }
])
text Keluaran
[
  { _id: 6000000, jumlah: 2 },
  { _id: 8000000, jumlah: 2 },
  { _id: 'di luar rentang', jumlah: 6 }
]

Dua interval terisi masing-masing oleh dua pegawai, sedangkan penampung berisi enam pegawai, yaitu empat pegawai bergaji di bawah enam juta dan dua pegawai bergaji di atas sepuluh juta. Perhitungan itu mudah diperiksa dari data tanaman karena rentang tengah hanya memuat 6200000 dan 7300000 pada interval pertama serta 8700000 dan 9100000 pada interval kedua. Bentuk ini cocok untuk laporan yang hanya menyorot rentang tertentu tanpa membuang sisanya dari penghitungan. Penampung default tidak muncul sama sekali apabila seluruh nilai berada di dalam rentang, sehingga keluarannya hanya memuat interval yang terisi.

Menentukan batas secara otomatis dengan $bucketAuto#

Tahap $bucketAuto menggantikan daftar boundaries dengan kunci buckets berupa jumlah interval yang diinginkan. Server mengurutkan nilai groupBy, membaginya menjadi kelompok yang berisi dokumen kira-kira sama banyak, lalu menurunkan batas interval dari nilai datanya sendiri. Field _id keluarannya berupa subdokumen min dan max, dan interval terakhir mencakup nilai tertingginya. Opsi granularity tersedia untuk memaksa batas mengikuti deret angka baku, sedangkan output tetap berisi akumulator sebagaimana $bucket.

mongosh
db.pegawai.aggregate([
  { $bucketAuto: {
      groupBy: "$gaji",
      buckets: 3,
      output: {
        jumlah: { $sum: 1 },
        daftarNama: { $push: "$nama" }
      }
  } }
])
text Keluaran
[
  {
    _id: { min: 4500000, max: 5600000 },
    jumlah: 3,
    daftarNama: [ 'Dewi Lestari', 'Joko Prasetyo', 'Hana Puspita' ]
  },
  {
    _id: { min: 5600000, max: 8700000 },
    jumlah: 3,
    daftarNama: [ 'Budi Santoso', 'Fitri Handayani', 'Citra Melati' ]
  },
  {
    _id: { min: 8700000, max: 11500000 },
    jumlah: 4,
    daftarNama: [ 'Indra Gunawan', 'Eka Pratama', 'Gilang Saputra', 'Ali Rahman' ]
  }
]

Sepuluh pegawai terbagi menjadi tiga interval berisi tiga, tiga, dan empat dokumen, dengan batas-batas yang berasal langsung dari nilai gaji seperti 5600000 dan 8700000. Pembagian ini tidak selalu sama banyak karena server mengutamakan batas yang berasal dari nilai data nyata. Batas yang dihasilkan juga dapat berubah apabila isi koleksinya berubah, karena batas itu diturunkan dari data pada saat pipeline dijalankan. Laporan yang menuntut interval konsisten antarperiode lebih cocok memakai $bucket dengan batas tetap, sedangkan $bucketAuto berguna apabila sebaran datanya belum diketahui. Opsi granularity yang memakai deret angka baku membuat batasnya lebih rapi dibaca, meskipun jumlah dokumen per kelompoknya menjadi tidak sama.

Kesalahan umum#

Kesalahan yang paling sering pada $bucket adalah menyempitkan boundaries tanpa menyediakan default, sehingga dokumen di luar rentang menimbulkan error saat eksekusi. Pipeline berikut hanya menampung rentang enam sampai sepuluh juta, sedangkan data memuat gaji di luar rentang itu.

mongosh
db.pegawai.aggregate([
  { $bucket: { groupBy: "$gaji", boundaries: [6000000, 8000000, 10000000] } }
])
text Keluaran
Uncaught
MongoServerError[Location7158303]: Executor error during aggregate command on namespace: pegawai.pegawai :: caused by :: $switch could not find a matching branch for an input, and no default was specified.

Pesan error tersebut menyatakan bahwa ada masukan tanpa cabang yang cocok dan tidak ada default. Perbaikannya adalah merentangkan batas pertama dan terakhir sampai melampaui nilai terkecil dan terbesar data, atau menambahkan opsi default sebagaimana contoh pada bagian sebelumnya.

Kesalahan kedua adalah menulis boundaries yang tidak terurut naik. Server memeriksa urutannya sebelum memproses dan menolak pipeline dengan pesan error. Pesan tersebut menyebut posisi elemen yang berselisih beserta kedua nilainya, sehingga perbaikannya cukup mengurutkan daftar batas tersebut. Selain itu, akumulator pada output hanya dihitung untuk dokumen di dalam intervalnya, sehingga jumlah dokumen pada seluruh interval perlu diperiksa apabila default tidak disediakan. Kedua kesalahan pada bagian ini baru muncul saat eksekusi, sehingga menguji pipeline dengan data kecil lebih dahulu menghemat waktu pencarian penyebabnya.

Ringkasan#

  • $bucket mengelompokkan dokumen menurut interval tetap dari boundaries, dengan _id keluaran berupa batas bawah setiap interval terisi.
  • Interval mencakup batas bawah dan membuka batas atas, sedangkan opsi default menampung nilai di luar seluruh rentang.
  • $bucketAuto menentukan batas dari data itu sendiri berdasarkan jumlah buckets yang diminta dan mencantumkannya pada _id berupa min dan max.
  • Daftar boundaries wajib terurut naik, dan dokumen di luar rentang menimbulkan error bila default tidak disediakan.

Referensi#