13-bo‘lim

$unwind, $lookup va $facet

Massivni qatorlarga yoyish, bo'sh massiv tuzog'i, to'plamlarni birlashtirish, $lookup bilan shartli moslash va $facet bilan bir necha hisobotni birga olish.

🕑 12 daqiqa o‘qish 📄 721 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. $unwind - massivni yoyish
  2. Bo'sh massiv tuzog'i
  3. includeArrayIndex
  4. $unwind va $group juftligi
  5. $lookup - to'plamlarni birlashtirish
  6. Shartli $lookup
  7. $facet - bir so'rovda bir nechta hisobot
  8. Xulosa

Bu uchta bosqich agregatsiyaning eng kuchli qismini tashkil qiladi: massivni yoyish, boshqa to'plamni ulash va bir so'rovda bir nechta hisobot olish.

JavaScript
db.buyurtma.insertMany([
  { _id: 101, mijoz_id: 1, sana: ISODate("2026-01-12T00:00:00Z"),
    qatorlar: [
      { kitob: "Otkan kunlar", soni: 2, narx: 85000 },
      { kitob: "Navoiy",       soni: 1, narx: 95000 }
    ] },
  { _id: 102, mijoz_id: 2, sana: ISODate("2026-02-03T00:00:00Z"),
    qatorlar: [
      { kitob: "Ufq",   soni: 3, narx: 64000 },
      { kitob: "Sarob", soni: 1, narx: 59000 },
      { kitob: "Navoiy", soni: 2, narx: 95000 }
    ] },
  { _id: 103, mijoz_id: 1, sana: ISODate("2026-02-22T00:00:00Z"),
    qatorlar: [
      { kitob: "Shum bola", soni: 4, narx: 47000 }
    ] },
  { _id: 104, mijoz_id: 3, sana: ISODate("2026-03-11T00:00:00Z"),
    qatorlar: [] }
])

db.mijoz.insertMany([
  { _id: 1, ism: "Husanboy", shahar: "Namangan" },
  { _id: 2, ism: "Malika",   shahar: "Toshkent" },
  { _id: 3, ism: "Nodira",   shahar: "Namangan" }
])

$unwind - massivni yoyish #

$unwind massivdagi har bir elementni alohida hujjatga aylantiradi:

JavaScript
db.buyurtma.aggregate([
  { $match: { _id: 101 } },
  { $unwind: "$qatorlar" },
  { $project: { _id: 1, kitob: "$qatorlar.kitob", soni: "$qatorlar.soni" } }
])
Natija
[
  { _id: 101, kitob: 'Otkan kunlar', soni: 2 },
  { _id: 101, kitob: 'Navoiy', soni: 1 }
]

Bitta hujjat ikkita bo'lib ketdi - chunki massivda ikkita element bor edi.

$unwind bitta hujjatni bir nechtaga aylantiradi Oldin: 1 hujjat { _id: 101, qatorlar: [ { kitob: "Otkan kunlar" }, { kitob: "Navoiy" } ] } $unwind { _id: 101, qatorlar: { kitob: "Otkan kunlar" } } { _id: 101, qatorlar: { kitob: "Navoiy" } } Bo'sh massivli hujjat butunlay yo'qoladi qatorlar: [] bo'lsa, $unwind undan nol hujjat yaratadi Saqlab qolish uchun preserveNullAndEmptyArrays kerak
Nomi ham shundan: massiv "o'ralgan" edi, $unwind uni "yozadi"

Bo'sh massiv tuzog'i #

To'rtta buyurtma bor, lekin _id: 104 da qatorlar bo'sh:

JavaScript
db.buyurtma.countDocuments()
db.buyurtma.aggregate([
  { $unwind: "$qatorlar" },
  { $group: { _id: null, hujjatlar: { $addToSet: "$_id" } } }
])
Natija
4
[ { _id: null, hujjatlar: [ 102, 101, 103 ] } ]

104 ro'yxatda yo'q - u butunlay yo'qoldi.

$unwind bo'sh massivli hujjatni tashlab yuboradi

Bu eng ko'p noto'g'ri hisobot beradigan tuzoqlardan biri.

"Har buyurtmadagi mahsulotlar sonini hisoblaymiz" degan so'rov yozasiz. $unwind qo'yasiz. Natijada bo'sh buyurtmalar hisobotdan butunlay yo'qoladi - go'yo ular umuman bo'lmagandek.

Xuddi shu narsa null va maydon yo'q holatlarida ham sodir bo'ladi.

Yechim - preserveNullAndEmptyArrays:

YozuvBo'sh massivli hujjat
{ $unwind: "$qatorlar" }Yo'qoladi
{ $unwind: { path: "$qatorlar", preserveNullAndEmptyArrays: true } }Qoladi, maydon null bo'ladi

Qachon qaysi biri: agar "mahsulotsiz buyurtma" ma'noga ega bo'lsa - saqlang. Agar u faqat xato bo'lsa - tashlang, lekin buni ataylab qiling.

JavaScript
db.buyurtma.aggregate([
  { $unwind: { path: "$qatorlar", preserveNullAndEmptyArrays: true } },
  { $group: { _id: "$_id", qatorlar: { $sum: 1 } } },
  { $sort: { _id: 1 } }
])
Natija
[
  { _id: 101, qatorlar: 2 },
  { _id: 102, qatorlar: 3 },
  { _id: 103, qatorlar: 1 },
  { _id: 104, qatorlar: 1 }
]

Endi 104 ham ko'rindi. E'tibor bering, uning qatorlari soni 1 - chunki $sum: 1 hujjatlarni sanaydi, massiv elementlarini emas. Aniq son kerak bo'lsa $size ishlatiladi.

includeArrayIndex #

Element massivda nechanchi o'rinda turganini bilish:

JavaScript
db.buyurtma.aggregate([
  { $match: { _id: 102 } },
  { $unwind: { path: "$qatorlar", includeArrayIndex: "orin" } },
  { $project: { _id: 0, orin: 1, kitob: "$qatorlar.kitob" } }
])
Natija
[
  { orin: Long('0'), kitob: 'Ufq' },
  { orin: Long('1'), kitob: 'Sarob' },
  { orin: Long('2'), kitob: 'Navoiy' }
]

$unwind va $group juftligi #

Eng ko'p ishlatiladigan naqsh - massivni yoyib, keyin qaytadan guruhlash:

JavaScript
db.buyurtma.aggregate([
  { $unwind: "$qatorlar" },
  { $group: {
      _id: "$qatorlar.kitob",
      nusxa: { $sum: "$qatorlar.soni" },
      tushum: { $sum: { $multiply: ["$qatorlar.soni", "$qatorlar.narx"] } }
  } },
  { $sort: { nusxa: -1 } }
])
Natija
[
  { _id: 'Shum bola', nusxa: 4, tushum: 188000 },
  { _id: 'Navoiy', nusxa: 3, tushum: 285000 },
  { _id: 'Ufq', nusxa: 3, tushum: 192000 },
  { _id: 'Otkan kunlar', nusxa: 2, tushum: 170000 },
  { _id: 'Sarob', nusxa: 1, tushum: 59000 }
]

$lookup - to'plamlarni birlashtirish #

JavaScript
db.buyurtma.aggregate([
  { $lookup: {
      from: "mijoz", localField: "mijoz_id",
      foreignField: "_id", as: "mijoz"
  } },
  { $unwind: "$mijoz" },
  { $project: { _id: 1, ism: "$mijoz.ism", shahar: "$mijoz.shahar" } },
  { $sort: { _id: 1 } }
])
Natija
[
  { _id: 101, ism: 'Husanboy', shahar: 'Namangan' },
  { _id: 102, ism: 'Malika', shahar: 'Toshkent' },
  { _id: 103, ism: 'Husanboy', shahar: 'Namangan' },
  { _id: 104, ism: 'Nodira', shahar: 'Namangan' }
]
MaydonMa'nosi
fromQaysi to'plamdan
localFieldShu quvurdagi maydon
foreignFieldBoshqa to'plamdagi maydon
asNatija qaysi nomga yoziladi

as har doim massiv beradi - shuning uchun yuqorida $unwind qo'shildi.

$lookup - bu SQL JOIN emas

Tashqi ko'rinishi o'xshash, lekin ichki mexanizmi butunlay boshqacha:

SQL JOIN$lookup
BajarilishiRejalashtiruvchi tanlaydiHar hujjat uchun alohida so'rov
IndeksHar ikki tomonforeignField da bo'lishi shart
NatijaTekis qatorlarMassiv maydon
Bir nechta shartOddiypipeline sintaksisi kerak

Eng muhim oqibat: $lookup 1 000 000 hujjatga qo'llansa, u 1 000 000 marta qidiruv qiladi. foreignField da indeks bo'lmasa, har biri butun to'plamni skanerlaydi.

Shuning uchun:

  1. $lookup dan oldin $match bilan hujjatlar sonini kamaytiring.
  2. foreignField da albatta indeks bo'lsin.
  3. Agar $lookup har so'rovda kerak bo'lsa - bu sxema noto'g'ri degan belgi. Ma'lumotni ichiga joylashni yoki kengaytirilgan havola naqshini o'ylang (10-bob).

Shartli $lookup #

Murakkabroq moslash uchun pipeline shakli bor:

JavaScript
db.mijoz.aggregate([
  { $lookup: {
      from: "buyurtma",
      let: { m: "$_id" },
      pipeline: [
        { $match: { $expr: { $eq: ["$mijoz_id", "$$m"] } } },
        { $match: { sana: { $gte: ISODate("2026-02-01T00:00:00Z") } } },
        { $project: { _id: 1, sana: 1 } }
      ],
      as: "yangi_buyurtmalar"
  } },
  { $project: { ism: 1, soni: { $size: "$yangi_buyurtmalar" } } },
  { $sort: { _id: 1 } }
])
Natija
[
  { _id: 1, ism: 'Husanboy', soni: 1 },
  { _id: 2, ism: 'Malika', soni: 1 },
  { _id: 3, ism: 'Nodira', soni: 1 }
]
YozuvMa'nosi
let: { m: "$_id" }Tashqi qiymatni o'zgaruvchiga oladi
$$mShu o'zgaruvchiga murojaat
$exprFiltr ichida ifoda ishlatishga ruxsat

$facet - bir so'rovda bir nechta hisobot #

JavaScript
db.buyurtma.aggregate([
  { $facet: {
      jami: [ { $count: "buyurtmalar" } ],
      mijoz_boyicha: [
        { $group: { _id: "$mijoz_id", soni: { $sum: 1 } } },
        { $sort: { _id: 1 } }
      ],
      eng_yangi: [
        { $sort: { sana: -1 } },
        { $limit: 2 },
        { $project: { _id: 1, sana: 1 } }
      ]
  } }
])
Natija
[
  {
    jami: [ { buyurtmalar: 4 } ],
    mijoz_boyicha: [ { _id: 1, soni: 2 }, { _id: 2, soni: 1 }, { _id: 3, soni: 1 } ],
    eng_yangi: [
      { _id: 104, sana: ISODate('2026-03-11T00:00:00.000Z') },
      { _id: 103, sana: ISODate('2026-02-22T00:00:00.000Z') }
    ]
  }
]

$facet ichidagi har bir quvur bir xil kirish hujjatlari ustida mustaqil ishlaydi.

$facet - sahifalash uchun ideal

Eng keng tarqalgan foydalanish: bitta so'rovda ham natijalarni, ham jami sonini olish.

Natija
{ $facet: {
    natijalar: [ { $skip: 0 }, { $limit: 20 } ],
    jami: [ { $count: "soni" } ]
} }

Usiz ikkita so'rov kerak bo'lardi: biri hujjatlar uchun, ikkinchisi countDocuments uchun. Ular orasida ma'lumot o'zgarsa, sahifalash chalkashadi.

Bitta eslatma: $facet ichidagi bosqichlar indeksdan foydalana olmaydi. Shuning uchun $match ni $facet dan oldin qo'ying - shunda filtrlash indeks bilan ishlaydi, $facet esa allaqachon kichraygan to'plam ustida ishlaydi.

Amaliy topshiriq
  1. $unwind bilan buyurtma qatorlarini yoying.
  2. Bir hujjatdan nechta hujjat chiqqanini sanang.
  3. Bo'sh massivli hujjat qayerga ketganini tekshiring.
  4. preserveNullAndEmptyArrays bilan uni qaytaring.
  5. includeArrayIndex bilan element o'rnini chiqaring.
  6. $unwind + $group bilan kitob bo'yicha hisobot tuzing.
  7. $lookup bilan mijoz ismini qo'shing.
  8. as nega massiv qaytarishini tushuntiring.
  9. $lookup uchun nima uchun indeks kerakligini yozing.
  10. $facet bilan uchta hisobotni bitta so'rovda oling.

Xulosa #

  • $unwind massivning har bir elementini alohida hujjatga aylantiradi.
  • Bo'sh massivli hujjat $unwind dan keyin yo'qoladi.
  • Uni saqlash uchun preserveNullAndEmptyArrays: true kerak.
  • includeArrayIndex element massivda nechanchi o'rinda turganini beradi.
  • $unwind + $group - massivli ma'lumot bo'yicha hisobotning asosiy naqshi.
  • $lookup boshqa to'plamni ulaydi va natijani massiv qilib beradi.
  • $lookup SQL JOIN emas: u har hujjat uchun alohida qidiruv qiladi.
  • foreignField da indeks bo'lishi shart, $match esa $lookup dan oldin.
  • pipeline shakli qo'shimcha shart va $expr ishlatishga imkon beradi.
  • $facet bir xil kirish ustida bir nechta mustaqil hisobot oladi - sahifalash uchun ideal.

Keyingi bo'limda hisoblangan maydonlar va shartli ifodalarni ko'ramiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.