Many-to-Many munosabat — junction table, so'rovlar
Muammodan boshlaylik
Mahsulotlarga teglar qo'shmoqchisiz: yangi, chegirma, xit, simsiz.
- AirPods Pro —
xit,simsiz - Qopqoq —
chegirma - Samsung Galaxy A55 —
yangi,chegirma,xit
Bitta mahsulotda ko'p teg. Bitta tegda ko'p mahsulot. Foreign key ni qayerga qo'yish kerak?
mahsulotlar.teg_id— mahsulotda faqat bitta teg bo'ladi ❌teglar.mahsulot_id— tegda faqat bitta mahsulot bo'ladi ❌
Ikkala tomon ham "ko'p". Bitta foreign key yetmaydi.
Yechim: oraliq (junction) jadval
Many-to-Many — uchinchi jadval orqali, ikkita One-to-Many ga bo'linadi:
CREATE TABLE teglar (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE mahsulot_teglar (
mahsulot_id INT UNSIGNED NOT NULL,
teg_id INT UNSIGNED NOT NULL,
PRIMARY KEY (mahsulot_id, teg_id),
FOREIGN KEY (mahsulot_id) REFERENCES mahsulotlar(id) ON DELETE CASCADE,
FOREIGN KEY (teg_id) REFERENCES teglar(id) ON DELETE CASCADE
);
mahsulotlar mahsulot_teglar teglar
id | nom mahsulot_id | teg_id id | nom
2 | Samsung ←──── 2 | 1 ───────→ 1 | yangi
←──── 2 | 2 ───────→ 2 | chegirma
←──── 2 | 3 ───────→ 3 | xit
5 | AirPods ←──── 5 | 3 ──┘ 4 | simsiz
←──── 5 | 4 ───────→
6 | Qopqoq ←──── 6 | 2 ──┘
Har bir qator — bitta bog'lanish: "2-mahsulot 1-tegga ega".
Muhim tafsilotlar:
PRIMARY KEY (mahsulot_id, teg_id)— composite key. Bir mahsulotga bir tegni ikki marta qo'shib bo'lmaydi. Alohidaidustuni shart emasON DELETE CASCADEikkala tomonda — mahsulot yoki teg o'chirilsa, bog'lanish ham o'chadi; mahsulotning o'zi esa teg o'chirilganda qoladi- Nomlash — ikki jadval nomidan:
mahsulot_teglar,talaba_kurslar,foydalanuvchi_rollar
Ma'lumot qo'shish
INSERT INTO teglar (nom) VALUES ('yangi'), ('chegirma'), ('xit'), ('simsiz');
INSERT INTO mahsulot_teglar (mahsulot_id, teg_id) VALUES
(2, 1), (2, 2), (2, 3),
(5, 3), (5, 4),
(6, 2);
So'rovlar
Mahsulot — teglari bilan
Ikki JOIN — oraliq jadval orqali:
SELECT p.nom, t.nom AS teg
FROM mahsulotlar p
JOIN mahsulot_teglar mt ON mt.mahsulot_id = p.id
JOIN teglar t ON t.id = mt.teg_id
WHERE p.id = 2;
Bir qatorda — GROUP_CONCAT bilan:
SELECT p.nom, GROUP_CONCAT(t.nom ORDER BY t.nom SEPARATOR ', ') AS teglar
FROM mahsulotlar p
LEFT JOIN mahsulot_teglar mt ON mt.mahsulot_id = p.id
LEFT JOIN teglar t ON t.id = mt.teg_id
GROUP BY p.id, p.nom;
+--------------------+---------------------+
| nom | teglar |
+--------------------+---------------------+
| iPhone 15 | NULL |
| Samsung Galaxy A55 | chegirma, xit, yangi|
| MacBook Air M3 | NULL |
| Lenovo IdeaPad | NULL |
| AirPods Pro | simsiz, xit |
| Qopqoq | chegirma |
| Sovg'a kartasi | NULL |
+--------------------+---------------------+
LEFT JOIN — tegsiz mahsulotlar ham chiqadi.
Teg bo'yicha mahsulotlar
SELECT p.nom, p.narx
FROM mahsulotlar p
JOIN mahsulot_teglar mt ON mt.mahsulot_id = p.id
JOIN teglar t ON t.id = mt.teg_id
WHERE t.nom = 'chegirma';
Bir nechta tegning hammasiga ega mahsulotlar
"Ham xit, ham chegirma" — qiyin, lekin ko'p kerak bo'ladigan savol:
SELECT p.nom
FROM mahsulotlar p
JOIN mahsulot_teglar mt ON mt.mahsulot_id = p.id
JOIN teglar t ON t.id = mt.teg_id
WHERE t.nom IN ('xit', 'chegirma')
GROUP BY p.id, p.nom
HAVING COUNT(DISTINCT t.id) = 2;
-- Samsung Galaxy A55
IN — "kamida bittasi bor"larni oladi. HAVING COUNT(...) = 2 — faqat ikkalasi ham borlarini qoldiradi. Bu naqsh internet do'konlardagi filtrlarda doim ishlatiladi.
Eng mashhur teglar
SELECT t.nom, COUNT(mt.mahsulot_id) AS mahsulotlar
FROM teglar t
LEFT JOIN mahsulot_teglar mt ON mt.teg_id = t.id
GROUP BY t.id, t.nom
ORDER BY mahsulotlar DESC;
Bog'lanishning o'z ma'lumoti bo'lsa
Ko'pincha bog'lanishning o'zi ma'lumotga ega. Talaba va kurs:
CREATE TABLE talaba_kurslar (
talaba_id INT UNSIGNED NOT NULL,
kurs_id INT UNSIGNED NOT NULL,
yozilgan DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
progress TINYINT UNSIGNED NOT NULL DEFAULT 0,
yakuniy_ball TINYINT UNSIGNED NULL,
PRIMARY KEY (talaba_id, kurs_id),
FOREIGN KEY (talaba_id) REFERENCES talabalar(id),
FOREIGN KEY (kurs_id) REFERENCES kurslar(id)
);
progress va yakuniy_ball — talabaga ham, kursga ham tegishli emas. Ular aynan shu talabaning aynan shu kursdagi ma'lumoti. Ularning yagona to'g'ri joyi — oraliq jadval.
Aslida buyurtma_qatorlari ham shunday jadval: buyurtmalar ↔ mahsulotlar orasidagi Many-to-Many, o'z ma'lumoti (soni, narx) bilan. Bunday hollarda oraliq jadval o'z nomiga va o'z id siga ega bo'lgan to'laqonli "ob'ekt"ga aylanadi.
Indekslar
PRIMARY KEY (mahsulot_id, teg_id) — mahsulot_id bo'yicha qidiruvni tezlashtiradi ("bu mahsulotning teglari"). Lekin teskari savol — "bu tegdagi mahsulotlar" — teg_id bo'yicha. Composite indeks faqat chap ustundan boshlab ishlaydi (31-dars), shuning uchun teskari yo'nalish uchun alohida indeks kerak. Yaxshiyamki, FOREIGN KEY (teg_id) uni avtomatik yaratadi.
Nima uchun shunday?
Nima uchun teglarni JSON massiv sifatida saqlamaslik kerak — teglar JSON = ["xit", "chegirma"]? Oddiyroq-ku.
Haqiqatan oddiyroq — faqat yozish uchun. Lekin keyin: "chegirma tegini aksiya ga o'zgartirish" — har bir mahsulotning JSON ini o'zgartirish kerak. "xit tegi bilan nechta mahsulot bor" — butun jadvalni ko'rib chiqish. Xato yozilgan chegrima tegini hech kim to'xtatmaydi. Oraliq jadval bilan: teg nomi bitta joyda, foreign key noto'g'ri tegni qabul qilmaydi, indekslar ikkala yo'nalishda ishlaydi. JSON — tuzilmasi oldindan noma'lum, kam qidiriladigan ma'lumot uchun.
Xulosa
Eslab qoling:
- Many-to-Many — ikkala tomonda ham "ko'p": mahsulot ↔ teg, talaba ↔ kurs
- Oraliq (junction) jadval: ikki foreign key +
PRIMARY KEY (a_id, b_id) - Nom:
mahsulot_teglar;ON DELETE CASCADE— bog'lanish o'chadi, ob'ektlar qoladi - So'rov — oraliq jadval orqali ikki JOIN
- "Hammasiga ega":
WHERE ... IN (...) GROUP BY ... HAVING COUNT(DISTINCT) = n - Bog'lanishning o'z ma'lumoti (ball, soni, sana) — oraliq jadvalda
- Teglarni vergul bilan yoki JSON da saqlamang
Keyingi dars
20-dars: One-to-One munosabat — qachon kerak