One-to-Many munosabat — loyihalash va so'rovlar

~20 daq · MySQL · JOIN va Munosabatlar

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, maqola2 ustunlarida saqlamang
  • NOT NULL / NULL va ON 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