Many-to-Many — qanday loyihalash
Muammodan boshlaylik
Talaba ko'p kursga yozilishi mumkin. Kursda ko'p talaba bo'lishi mumkin.
Bu Many-to-Many — ikki tomon ham ko'p.
SQL da bu oson: oraliq jadval (junction table). MongoDB da ham shunga o'xshash yondashuv bor — lekin bir nechta variant mavjud.
Variant 1 — Ikki tomonda ham ID massivi
// talabalar
{
"_id": "t1",
"ism": "Jasur",
"kurs_ids": ["k1", "k2"]
}
// kurslar
{
"_id": "k1",
"nom": "MongoDB",
"talaba_ids": ["t1", "t2", "t3"]
}
talabalar kurslar
───────── ───────
t1 → [k1, k2] k1 ← [t1, t2, t3]
t2 → [k1, k3] k2 ← [t1]
k3 ← [t2]
Qachon: Ikki tomonda ham navigatsiya tez-tez kerak bo'lganda.
// Jasurning kurslari
let talaba = db.talabalar.findOne({ _id: "t1" })
db.kurslar.find({ _id: { $in: talaba.kurs_ids } })
// MongoDB kursidagi talabalar
let kurs = db.kurslar.findOne({ _id: "k1" })
db.talabalar.find({ _id: { $in: kurs.talaba_ids } })
Muammo: Ikki joyni yangilash kerak — yangi yozilganda ham talabalar ham kurslar da o'zgarish kerak.
Variant 2 — Bir tomonda ID massivi
Faqat bir tomonda ID massivi, ikkinchi tomonda yo'q.
// talabalar
{
"_id": "t1",
"ism": "Jasur",
"kurs_ids": ["k1", "k2"] // ← faqat shu yerda
}
// kurslar — talaba_ids yo'q
{ "_id": "k1", "nom": "MongoDB" }
{ "_id": "k2", "nom": "React" }
talabalar kurslar
───────── ───────
t1 → [k1, k2] → k1, k2
t2 → [k1, k3] → k1, k3
Qachon: Asosan bir tomondan navigatsiya bo'lganda (talaba → kurslari).
// Jasurning kurslari — oson
let talaba = db.talabalar.findOne({ _id: "t1" })
db.kurslar.find({ _id: { $in: talaba.kurs_ids } })
// MongoDB kursidagi talabalar — $in bilan
db.talabalar.find({ kurs_ids: "k1" })
// ↑ array ichida qidiradi
Variant 3 — Oraliq collection (Junction)
SQL dagi junction table ga o'xshash. Qo'shimcha ma'lumot bo'lganda ideal.
// talabalar
{ "_id": "t1", "ism": "Jasur" }
// kurslar
{ "_id": "k1", "nom": "MongoDB" }
// yozilishlar (junction)
{
"_id": "y1",
"talaba_id": "t1",
"kurs_id": "k1",
"yozilgan": "2024-01-15", // ← qo'shimcha ma'lumot
"holat": "faol" // ← qo'shimcha ma'lumot
}
talabalar yozilishlar kurslar
───────── ─────────── ───────
t1 ←── talaba_id k1
kurs_id ──→ k1
Qachon: Munosabat haqida qo'shimcha ma'lumot kerak bo'lganda — qachon yozildi, holati nima, to'landi yoki to'lanmadi.
// Jasurning kurslari va holati
db.yozilishlar.find({ talaba_id: "t1" })
// MongoDB kursidagi faol talabalar
db.yozilishlar.find({ kurs_id: "k1", holat: "faol" })
Qaysi variant?
Savol: Munosabat haqida qo'shimcha ma'lumot kerakmi?
HA → Variant 3 (oraliq collection)
Savol: Ikki tomonda ham tez navigatsiya kerakmi?
HA → Variant 1 (ikki tomonda ID)
Savol: Faqat bir tomondan navigatsiya?
HA → Variant 2 (bir tomonda ID)
Real misol — mahsulot va teglar
Mahsulotning ko'p tegi bor. Bir teg ko'p mahsulotda bor.
// ✅ Variant 2 — mahsulot tomonida teglar
{
"_id": "m1",
"nom": "iPhone 15",
"teglar": ["telefon", "apple", "smartfon"]
}
// "apple" tegi bo'lgan mahsulotlar
db.mahsulotlar.find({ teglar: "apple" })
// Natija:
// [{ nom: "iPhone 15", ... }, { nom: "MacBook", ... }]
Teglar uchun oraliq collection kerak emas — qo'shimcha ma'lumot yo'q, soni kam.
Real misol — foydalanuvchi va rollar
// foydalanuvchilar
{
"_id": "u1",
"ism": "Jasur",
"rol_ids": ["r1", "r3"]
}
// rollar
{ "_id": "r1", "nom": "admin" }
{ "_id": "r2", "nom": "moderator" }
{ "_id": "r3", "nom": "muharrir" }
// Jasurning rollari
let user = db.users.findOne({ _id: "u1" })
db.rollar.find({ _id: { $in: user.rol_ids } })
// Natija:
// [{ nom: "admin" }, { nom: "muharrir" }]
// "admin" rolidagi foydalanuvchilar
db.users.find({ rol_ids: "r1" })
Tez-tez uchraydigan xatolar
ID massivi cheksiz o'sishi
// ❌ — foydalanuvchi minglab mahsulotni yoqtirsa
{
"_id": "u1",
"yoqtirgan_ids": ["m1", "m2", ...] // 50 000 ta
}
// ✅ — alohida collection
// likes
{ "foydalanuvchi_id": "u1", "mahsulot_id": "m1", "vaqt": "..." }
Ikki tomonda yangilashni unutish (Variant 1)
// ❌ — faqat bir tomonda yangilash
db.talabalar.updateOne(
{ _id: "t1" },
{ $push: { kurs_ids: "k4" } }
)
// kurslar.talaba_ids ga "t1" qo'shilmadi!
// ✅ — ikkalasini birga yangilash
db.talabalar.updateOne({ _id: "t1" }, { $push: { kurs_ids: "k4" } })
db.kurslar.updateOne({ _id: "k4" }, { $push: { talaba_ids: "t1" } })
// Yoki transaction bilan (31-dars)
Xulosa
- Variant 1 (ikki tomonda ID) — ikki tomonda ham tez navigatsiya, lekin ikki joyni yangilash kerak
- Variant 2 (bir tomonda ID) — sodda, faqat bir tomondan navigatsiya bo'lganda
- Variant 3 (oraliq collection) — munosabat haqida qo'shimcha ma'lumot bo'lganda
- Teglar, rollar — odatda embedded array yetarli
- Ko'p bo'ladigan munosabatlar (likes, follows) — oraliq collection
Keyingi dars: 16-dars — Schema Patterns — Bucket, Outlier, Computed, Subset