Relasi one-to-many adalah relasi yang paling sering kita jumpai: satu pelanggan punya banyak alamat, satu pesanan memuat banyak item, satu film dibintangi banyak aktor. Dokumentasi resmi MongoDB memetakan relasi ini ke tiga pola penyimpanan — embed array pada induk, referensi pada anak, dan array referensi pada induk — dan tiap pola punya wilayah kerja yang jelas. Kita uji ketiganya dengan tiga pasangan entitas toko buku dan dunia film.

Pola 1: Embed Array pada Induk #

Ketika jumlah anak sedikit, terbatas, dan hampir selalu dibaca bersama induknya, anak cukup menjadi array subdocument di dalam induk — pola “one-to-few” pada terminologi dokumentasi. Alamat pelanggan adalah contoh klasik: jarang lebih dari tiga, tidak pernah diakses tanpa pelanggannya:

js
use latihan_modeling
db.dropDatabase()
db.pelanggan.insertMany([
  {
    _id: 1, nama: "Budi",
    alamat: [
      { label: "rumah", kota: "Bandung", jalan: "Jl. Merdeka 10" },
      { label: "kantor", kota: "Jakarta", jalan: "Jl. Sudirman 45" }
    ]
  },
  {
    _id: 2, nama: "Siti",
    alamat: [ { label: "rumah", kota: "Bandung", jalan: "Jl. Dago 88" } ]
  }
])
db.pelanggan.find({ "alamat.kota": "Bandung" }, { nama: 1 })

Output yang Dihasilkan:

text
switched to db latihan_modeling
{ ok: 1, dropped: 'latihan_modeling' }
{ acknowledged: true, insertedIds: { '0': 1, '1': 2 } }
[ { _id: 1, nama: 'Budi' }, { _id: 2, nama: 'Siti' } ]

Satu query find dengan path bertitik "alamat.kota" menemukan pelanggan yang punya alamat di Bandung — tidak ada collection alamat, tidak ada join. Menambah alamat baru pun cukup satu $push ke document pelanggan.

Tiga ciri yang membuat alamat cocok untuk pola ini:

  1. Milik induk sepenuhnya — alamat “rumah” hanya bermakna bersama pemiliknya; tidak ada use case yang membaca alamat tanpa pelanggannya.
  2. Jumlahnya wajar — dua, tiga, mungkin empat per pelanggan; array tidak pernah membesar tanpa kontrol.
  3. Bentuknya seragam — semua alamat punya label, kota, jalan, sehingga satu query path bertitik melayani semuanya.

Pola 2: Referensi pada Anak #

Saat jumlah anak bisa mencapai ratusan atau tumbuh tanpa batas yang jelas, array pada induk menjadi rentan: document induk membesar setiap kali anak lahir. Pola kedua membalik arah penunjukan — setiap anak menyimpan referensi induknya, dan anak-anak hidup sebagai document mandiri di collection sendiri:

js
db.pesanan.insertMany([
  { _id: 501, pelangganId: 1, tanggal: ISODate("2026-12-05T10:00:00Z"), total: 187000 },
  { _id: 502, pelangganId: 2, tanggal: ISODate("2026-12-06T09:30:00Z"), total: 98000 }
])
db.item_pesanan.insertMany([
  { pesananId: 501, buku: "Bumi", jumlah: 1, harga: 89000 },
  { pesananId: 501, buku: "Filosofi Teras", jumlah: 1, harga: 98000 },
  { pesananId: 502, buku: "Laut Bercerita", jumlah: 1, harga: 98000 }
])
db.item_pesanan.find({ pesananId: 501 })

Output yang Dihasilkan:

text
{ acknowledged: true, insertedIds: { '0': 501, '1': 502 } }
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad398fbfe08c930898cf842'),
    '1': ObjectId('6ad398fbfe08c930898cf843'),
    '2': ObjectId('6ad398fbfe08c930898cf844')
  }
}
[
  {
    _id: ObjectId('6ad398fbfe08c930898cf842'),
    pesananId: 501,
    buku: 'Bumi',
    jumlah: 1,
    harga: 89000
  },
  {
    _id: ObjectId('6ad398fbfe08c930898cf843'),
    pesananId: 501,
    buku: 'Filosofi Teras',
    jumlah: 1,
    harga: 98000
  }
]

Pola akses rinci pesanan tetap terlayani: satu find pada pesananId mengembalikan semua itemnya, dan index pada field itu membuat pencarian sebanyak apa pun tetap cepat. Sementara itu document pesanan sendiri tetap kecil — informasi total bahkan sudah tersimpan di induk tanpa perlu membaca item satu per satu.

Pola 3: Array Referensi pada Induk #

Pola ketiga berada di tengah: anak disimpan sebagai document mandiri di collection sendiri, tetapi induk menyimpan array berisi _id anak-anaknya. Cocok ketika anak adalah entitas yang berdiri sendiri, dipakai bersama oleh banyak induk, dan jumlahnya per induk wajar (belasan, bukan ribuan). Film dan aktor adalah contoh klasiknya — aktor yang sama membintangi banyak film:

js
db.aktor.insertMany([
  { _id: 1, nama: "Reza Rahadian" },
  { _id: 2, nama: "Christine Hakim" },
  { _id: 3, nama: "Nicholas Saputra" }
])
db.film.insertMany([
  { _id: 10, judul: "Habibie & Ainun", tahun: 2012, aktorId: [1, 2] },
  { _id: 11, judul: "Ada Apa dengan Cinta?", tahun: 2002, aktorId: [3, 2] }
])
db.film.aggregate([
  { $match: { _id: 10 } },
  { $lookup: { from: "aktor", localField: "aktorId", foreignField: "_id", as: "pemain" } },
  { $project: { judul: 1, tahun: 1, "pemain.nama": 1 } }
])

Output yang Dihasilkan:

text
{ acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3 } }
{ acknowledged: true, insertedIds: { '0': 10, '1': 11 } }
[
  {
    _id: 10,
    judul: 'Habibie & Ainun',
    tahun: 2012,
    pemain: [ { nama: 'Reza Rahadian' }, { nama: 'Christine Hakim' } ]
  }
]

$lookup menggabungkan array aktorId dengan collection aktor dalam satu operasi — lokal field yang berupa array tetap dicocokkan elemen demi elemen. Kebalikan arahnya pun sederhana, cukup query containment pada array:

js
db.film.find({ aktorId: 2 }, { judul: 1, tahun: 1 })

Output yang Dihasilkan:

text
[
  { _id: 10, judul: 'Habibie & Ainun', tahun: 2012 },
  { _id: 11, judul: 'Ada Apa dengan Cinta?', tahun: 2002 }
]

Memilih di Antara Tiga Pola #

PertimbanganPola 1: Embed arrayPola 2: Ref di anakPola 3: Array ref di induk
Jumlah anak per indukSedikit, terbatasBanyak hingga tak terbatasSedang, wajar
Anak dipakai induk lain?TidakBolehYa (entitas mandiri)
Membaca anak tanpa indukJarangSeringKadang
Menambah anak$push satu documentinsertOne baruinsertOne + update array induk
Ukuran document indukTumbuh mengikuti anakTetapTumbuh tipis (hanya _id)
Query “anak dari induk X”Satu query pada indukQuery pada collection anak$lookup atau dua query

Ketiga pola itu tidak saling menggantikan total — dalam satu aplikasi toko buku, ketiganya bisa hidup berdampingan: alamat embed pada pelanggan, item terpisah dari pesanan, dan buku menyimpan array penulisId.

Rangkuman #

  • Embed array (one-to-few): anak sedikit, terbatas, selalu dibaca bersama induk — satu query untuk semua.
  • Referensi pada anak: anak banyak atau tak terbatas; setiap anak membawa indukId; induk tetap ramping.
  • Array referensi pada induk: anak entitas mandiri yang dipakai bersama; induk menyimpan array _id dan menggabungkan dengan $lookup.
  • Satu aplikasi nyata lazim memakai ketiga pola sekaligus, per relasi masing-masing.

Langkah lebih rumit berikutnya: relasi yang jumlahnya banyak di kedua sisi — many-to-many.