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:
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:
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:
- Milik induk sepenuhnya — alamat “rumah” hanya bermakna bersama pemiliknya; tidak ada use case yang membaca alamat tanpa pelanggannya.
- Jumlahnya wajar — dua, tiga, mungkin empat per pelanggan; array tidak pernah membesar tanpa kontrol.
- 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:
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:
{ 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:
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:
{ 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:
db.film.find({ aktorId: 2 }, { judul: 1, tahun: 1 })Output yang Dihasilkan:
[
{ _id: 10, judul: 'Habibie & Ainun', tahun: 2012 },
{ _id: 11, judul: 'Ada Apa dengan Cinta?', tahun: 2002 }
]Memilih di Antara Tiga Pola #
| Pertimbangan | Pola 1: Embed array | Pola 2: Ref di anak | Pola 3: Array ref di induk |
|---|---|---|---|
| Jumlah anak per induk | Sedikit, terbatas | Banyak hingga tak terbatas | Sedang, wajar |
| Anak dipakai induk lain? | Tidak | Boleh | Ya (entitas mandiri) |
| Membaca anak tanpa induk | Jarang | Sering | Kadang |
| Menambah anak | $push satu document | insertOne baru | insertOne + update array induk |
| Ukuran document induk | Tumbuh mengikuti anak | Tetap | Tumbuh tipis (hanya _id) |
| Query “anak dari induk X” | Satu query pada induk | Query 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
_iddan 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.