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.
Ushbu bo‘lim mundarijasi
Bu uchta bosqich agregatsiyaning eng kuchli qismini tashkil qiladi: massivni yoyish, boshqa to'plamni ulash va bir so'rovda bir nechta hisobot olish.
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:
db.buyurtma.aggregate([
{ $match: { _id: 101 } },
{ $unwind: "$qatorlar" },
{ $project: { _id: 1, kitob: "$qatorlar.kitob", soni: "$qatorlar.soni" } }
])
[
{ _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.
Bo'sh massiv tuzog'i #
To'rtta buyurtma bor, lekin _id: 104 da qatorlar bo'sh:
db.buyurtma.countDocuments()
db.buyurtma.aggregate([
{ $unwind: "$qatorlar" },
{ $group: { _id: null, hujjatlar: { $addToSet: "$_id" } } }
])
4
[ { _id: null, hujjatlar: [ 102, 101, 103 ] } ]
104 ro'yxatda yo'q - u butunlay yo'qoldi.
$unwind bo'sh massivli hujjatni tashlab yuboradiBu 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:
| Yozuv | Bo'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.
db.buyurtma.aggregate([
{ $unwind: { path: "$qatorlar", preserveNullAndEmptyArrays: true } },
{ $group: { _id: "$_id", qatorlar: { $sum: 1 } } },
{ $sort: { _id: 1 } }
])
[
{ _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:
db.buyurtma.aggregate([
{ $match: { _id: 102 } },
{ $unwind: { path: "$qatorlar", includeArrayIndex: "orin" } },
{ $project: { _id: 0, orin: 1, kitob: "$qatorlar.kitob" } }
])
[
{ 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:
db.buyurtma.aggregate([
{ $unwind: "$qatorlar" },
{ $group: {
_id: "$qatorlar.kitob",
nusxa: { $sum: "$qatorlar.soni" },
tushum: { $sum: { $multiply: ["$qatorlar.soni", "$qatorlar.narx"] } }
} },
{ $sort: { nusxa: -1 } }
])
[
{ _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 #
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 } }
])
[
{ _id: 101, ism: 'Husanboy', shahar: 'Namangan' },
{ _id: 102, ism: 'Malika', shahar: 'Toshkent' },
{ _id: 103, ism: 'Husanboy', shahar: 'Namangan' },
{ _id: 104, ism: 'Nodira', shahar: 'Namangan' }
]
| Maydon | Ma'nosi |
|---|---|
from | Qaysi to'plamdan |
localField | Shu quvurdagi maydon |
foreignField | Boshqa to'plamdagi maydon |
as | Natija qaysi nomga yoziladi |
as har doim massiv beradi - shuning uchun yuqorida
$unwind qo'shildi.
$lookup - bu SQL JOIN emasTashqi ko'rinishi o'xshash, lekin ichki mexanizmi butunlay boshqacha:
SQL JOIN | $lookup | |
|---|---|---|
| Bajarilishi | Rejalashtiruvchi tanlaydi | Har hujjat uchun alohida so'rov |
| Indeks | Har ikki tomon | foreignField da bo'lishi shart |
| Natija | Tekis qatorlar | Massiv maydon |
| Bir nechta shart | Oddiy | pipeline 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:
$lookupdan oldin$matchbilan hujjatlar sonini kamaytiring.foreignFieldda albatta indeks bo'lsin.- Agar
$lookuphar 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:
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 } }
])
[
{ _id: 1, ism: 'Husanboy', soni: 1 },
{ _id: 2, ism: 'Malika', soni: 1 },
{ _id: 3, ism: 'Nodira', soni: 1 }
]
| Yozuv | Ma'nosi |
|---|---|
let: { m: "$_id" } | Tashqi qiymatni o'zgaruvchiga oladi |
$$m | Shu o'zgaruvchiga murojaat |
$expr | Filtr ichida ifoda ishlatishga ruxsat |
$facet - bir so'rovda bir nechta hisobot #
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 } }
]
} }
])
[
{
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 idealEng keng tarqalgan foydalanish: bitta so'rovda ham natijalarni, ham jami sonini olish.
{ $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.
$unwindbilan buyurtma qatorlarini yoying.- Bir hujjatdan nechta hujjat chiqqanini sanang.
- Bo'sh massivli hujjat qayerga ketganini tekshiring.
preserveNullAndEmptyArraysbilan uni qaytaring.includeArrayIndexbilan element o'rnini chiqaring.$unwind+$groupbilan kitob bo'yicha hisobot tuzing.$lookupbilan mijoz ismini qo'shing.asnega massiv qaytarishini tushuntiring.$lookupuchun nima uchun indeks kerakligini yozing.$facetbilan uchta hisobotni bitta so'rovda oling.
Xulosa #
$unwindmassivning har bir elementini alohida hujjatga aylantiradi.- Bo'sh massivli hujjat
$unwinddan keyin yo'qoladi. - Uni saqlash uchun
preserveNullAndEmptyArrays: truekerak. includeArrayIndexelement massivda nechanchi o'rinda turganini beradi.$unwind+$group- massivli ma'lumot bo'yicha hisobotning asosiy naqshi.$lookupboshqa to'plamni ulaydi va natijani massiv qilib beradi.$lookupSQLJOINemas: u har hujjat uchun alohida qidiruv qiladi.foreignFieldda indeks bo'lishi shart,$matchesa$lookupdan oldin.pipelineshakli qo'shimcha shart va$exprishlatishga imkon beradi.$facetbir xil kirish ustida bir nechta mustaqil hisobot oladi - sahifalash uchun ideal.
Keyingi bo'limda hisoblangan maydonlar va shartli ifodalarni ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.