One-to-Many munosabat — loyihalash va so'rovlar
Muammodan boshlaylik
Blog loyihalashtiryapsiz. Har bir muallifning ko'p maqolasi bor. Maqolalarni muallif jadvalida qanday saqlash kerak?
❌ 1-urinish: ustunlar
mualliflar: id | ism | maqola1 | maqola2 | maqola3
❌ 2-urinish: vergul bilan ro'yxat
mualliflar: id | ism | maqolalar
1 | Ali | "PHP asoslari, MySQL JOIN, Laravel kirish"
1-urinish: 4-maqola yozilsa — jadvalni o'zgartirish kerak. Ko'pchilikda 1 ta maqola — qolgan ustunlar bo'sh. 2-urinish: "MySQL JOIN maqolasining muallifi kim?" — matn ichidan qidirish. Maqolaga sana yoki ko'rishlar soni qo'shib bo'lmaydi.
To'g'ri yechim — One-to-Many munosabat. Bu relational bazadagi eng ko'p uchraydigan munosabat turi.
One-to-Many nima?
Bitta ota qatoriga ko'p bola qatori mos keladi. Har bir bola esa faqat bitta otaga tegishli.
Hayotdan misollar:
- Bitta mijoz → ko'p buyurtma
- Bitta toifa → ko'p mahsulot
- Bitta muallif → ko'p maqola
- Bitta buyurtma → ko'p buyurtma qatori
- Bitta maqola → ko'p izoh
Qoida: foreign key "ko'p" tomonda
CREATE TABLE mualliflar (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
ism VARCHAR(100) NOT NULL
);
CREATE TABLE maqolalar (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
muallif_id INT UNSIGNED NOT NULL, -- ← foreign key BOLA jadvalda
sarlavha VARCHAR(200) NOT NULL,
yaratilgan DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (muallif_id) REFERENCES mualliflar(id)
);
mualliflar maqolalar
id | ism id | muallif_id | sarlavha
1 | Ali ←──┬───── 1 | 1 | PHP asoslari
├───── 2 | 1 | MySQL JOIN
└───── 3 | 1 | Laravel kirish
2 | Dilnoza ←──────── 4 | 2 | CSS Grid
Har bir maqola "men 1-muallifnikiman" deydi. Muallif o'z maqolalari ro'yxatini saqlamaydi — bu ro'yxat so'rov orqali olinadi.
Tekshirish savoli: "Bitta maqolaning nechta muallifi bo'lishi mumkin?" — bitta. "Bitta muallifning nechta maqolasi?" — ko'p. Foreign key — javob "bitta" bo'lgan tomonga ishora qiladi va "ko'p" tomonda turadi.
Loyihalash qarorlari
NOT NULL yoki NULL?
muallif_id INT UNSIGNED NOT NULL -- har bir maqolaning muallifi bo'lishi SHART
toifa_id INT UNSIGNED NULL -- mahsulotning toifasi bo'lmasligi MUMKIN
Biznes savoli: "bola otasiz yashay oladimi?"
ON DELETE
| Munosabat | Ota o'chirilsa | Tanlov |
|---|---|---|
| Buyurtma → qatorlar | Qatorlar ma'nosiz | CASCADE |
| Mijoz → buyurtmalar | Buyurtmalar saqlanishi shart | RESTRICT |
| Toifa → mahsulotlar | Mahsulotlar "toifasiz" qoladi | SET NULL |
| Maqola → izohlar | Izohlar ma'nosiz | CASCADE |
(7-dars.)
Tipik so'rovlar
1. Bola — otasi bilan
SELECT p.nom, p.narx, t.nom AS toifa
FROM mahsulotlar p
LEFT JOIN toifalar t ON t.id = p.toifa_id;
2. Bitta otaning barcha bolalari
SELECT id, holat, yaratilgan
FROM buyurtmalar
WHERE mijoz_id = 1
ORDER BY yaratilgan DESC;
JOIN shart ham emas — foreign key bo'yicha oddiy WHERE. mijoz_id da indeks bor — tez.
3. Ota — bolalar soni / yig'indisi bilan
SELECT t.nom, COUNT(p.id) AS mahsulotlar, COALESCE(SUM(p.soni), 0) AS dona
FROM toifalar t
LEFT JOIN mahsulotlar p ON p.toifa_id = t.id
GROUP BY t.id, t.nom;
4. Bolasi yo'q otalar
SELECT m.ism FROM mijozlar m
LEFT JOIN buyurtmalar b ON b.mijoz_id = m.id
WHERE b.id IS NULL;
5. Har bir otaning eng oxirgi bolasi
"Har bir mijozning oxirgi buyurtmasi" — klassik va qiyinroq savol:
SELECT m.ism, b.id, b.yaratilgan
FROM mijozlar m
JOIN buyurtmalar b ON b.mijoz_id = m.id
WHERE b.yaratilgan = (
SELECT MAX(b2.yaratilgan) FROM buyurtmalar b2 WHERE b2.mijoz_id = m.id
);
+-----------------+----+---------------------+
| ism | id | yaratilgan |
+-----------------+----+---------------------+
| Aziz Karimov | 2 | 2026-10-12 18:40:00 |
| Malika Rahimova | 5 | 2026-11-01 16:20:00 |
| Jasur Toshev | 4 | 2026-10-20 09:30:00 |
+-----------------+----+---------------------+
Bu correlated subquery (22-dars). Window function bilan yanada chiroyli yechim — 29-darsda.
Ilovada: N+1 muammosi
Ilovada (PHP, Node) mijozlar ro'yxatini buyurtmalari bilan chiqarish kerak. Sodda kod:
mijozlar = SELECT * FROM mijozlar ← 1 so'rov
har bir mijoz uchun:
SELECT * FROM buyurtmalar WHERE mijoz_id = ? ← N so'rov
100 ta mijoz → 101 ta so'rov. Har biri tarmoq orqali bormoqda. Sahifa sekinlashadi. Bu N+1 muammosi — ORM lar bilan ishlaganda eng ko'p uchraydigan muammo.
Yechim — 2 ta so'rov:
SELECT id, ism FROM mijozlar; -- 1
SELECT * FROM buyurtmalar WHERE mijoz_id IN (1, 2, 3, ...); -- 2
Keyin ilovada mijoz_id bo'yicha guruhlaysiz. Yoki bitta JOIN so'rov. Laravel da with('buyurtmalar'), Prisma da include aynan shunday ishlaydi (34-dars, 57-59 darslar).
Bola jadvalda nimani saqlash kerak?
buyurtma_qatorlari da nima uchun narx ham bor, mahsulotlar.narx dan olsa bo'lmaydimi?
buyurtma_qatorlari: buyurtma_id | mahsulot_id | soni | narx
mahsulotlar.narx — hozirgi narx. Ertaga u o'zgaradi. buyurtma_qatorlari.narx — sotilgan paytdagi narx, tarixiy fakt. Agar uni saqlamasangiz — iyuldagi chek summasi narx o'zgargan sari o'zgaradi.
Bu takrorlash emas: bu boshqa ma'lumot (vaqtning bir lahzasidagi narx). Xuddi shunday: buyurtmadagi yetkazish manzili — mijozning hozirgi manzilidan nusxa olinadi, chunki mijoz keyin ko'chishi mumkin.
Nima uchun shunday?
Nima uchun foreign key "ko'p" tomonda turadi, "bitta" tomonda emas?
Relational jadvalda har bir katakda bitta qiymat bo'ladi. Maqola uchun "muallifim kim?" — bitta qiymat, bitta katakka sig'adi. Muallif uchun "maqolalarim qaysilar?" — ko'p qiymat, bitta katakka sig'maydi. Agar ro'yxatni bitta katakka tiqsangiz (vergul bilan) — SQL u bilan ishlay olmaydi: indeks yo'q, JOIN yo'q, foreign key tekshiruvi yo'q. Shuning uchun ro'yxat "teskari" saqlanadi: har bir element o'z otasini ko'rsatadi.
Xulosa
Eslab qoling:
- One-to-Many — eng ko'p uchraydigan munosabat: mijoz → buyurtmalar
- Foreign key bola ("ko'p") jadvalda:
buyurtmalar.mijoz_id - Ro'yxatni vergul bilan bitta ustunda yoki
maqola1, maqola2ustunlarida saqlamang NOT NULL/NULLvaON DELETE— biznes qoidasiga qarab- Otasining bolalari:
WHERE ota_id = ?; soni bilan:LEFT JOIN ... GROUP BY - Ilovada N+1 dan qoching —
IN (...)yoki JOIN - Tarixiy qiymatlar (narx, manzil) bola jadvalda nusxalanadi
Keyingi dars
19-dars: Many-to-Many munosabat — junction table, so'rovlar