Many-to-Many — qanday loyihalash

~20 daq · MongoDB · Schema Dizayn

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