Many-to-Many — junction table, so'rovlar
Muammodan boshlaylik
Talaba va kurslar. Bir talaba bir nechta kursga yozilishi mumkin. Bir kursda bir nechta talaba bo'lishi mumkin.
Bu One-to-Many emas — chunki ikkala tomonda ham "ko'p" bor.
Ali → JavaScript, React, Node.js
Vali → Python, JavaScript
Soli → React
JavaScript → Ali, Vali
React → Ali, Soli
Node.js → Ali
Python → Vali
Buni qanday saqlash kerak? talabalar jadvaliga kurslar ustunini qo'shib bo'lmaydi — bitta ustunda ro'yxat saqlanmaydi. kurslar jadvaliga talabalar ustunini qo'shib ham bo'lmaydi — xuddi shu muammo.
Many-to-Many nima?
Many-to-Many — ikkala tomondagi har bir yozuv boshqa tomonning bir nechta yozuviga bog'liq bo'lishi mumkin bo'lgan munosabat.
Hayotiy o'xshatma: kitoblar va mualliflar. Bir kitobni bir nechta muallif yozishi mumkin. Bir muallif bir nechta kitob yozishi mumkin. Ikki tomondan ham "ko'p".
Boshqa misollar:
- Mahsulot va teglar: bir mahsulot ko'p tegga ega, bir teg ko'p mahsulotda
- Foydalanuvchi va rol: bir foydalanuvchi ko'p rolga ega, bir rol ko'p foydalanuvchiga berilgan
- Film va aktyorlar: bir filmda ko'p aktyor, bir aktyor ko'p filmda
Yechim — Junction Table
Many-to-Many ni to'g'ridan-to'g'ri ikki jadval orasida ifodalab bo'lmaydi. Buning uchun uchinchi jadval kerak — junction table (bog'lovchi jadval).
Bu jadval ikkala jadvalning IDlarini saqlaydi:
talabalar talaba_kurs kurslar
----------- --------------- -----------
id | ism talaba_id | kurs_id id | nomi
---+------ ----------+-------- ---+-------
1 | Ali 1 | 1 1 | JS
2 | Vali 1 | 2 2 | React
3 | Soli 1 | 3 3 | Node.js
2 | 4 4 | Python
2 | 1
3 | 2
Vizual:
Ali ──────┬──── JavaScript
├──── React
└──── Node.js
Vali ─────┬──── Python
└──── JavaScript
Soli ────────── React
Junction table har bir bog'lanishni bitta qator sifatida saqlaydi. Ali JavaScript ga yozilgan — bu (1, 1) qatori. Ali React ga yozilgan — bu (1, 2) qatori.
Jadvallarni yaratish
CREATE TABLE talabalar (
id SERIAL PRIMARY KEY,
ism TEXT NOT NULL
);
CREATE TABLE kurslar (
id SERIAL PRIMARY KEY,
nomi TEXT NOT NULL
);
-- Junction table — ikkala jadvalga foreign key
CREATE TABLE talaba_kurs (
talaba_id INT NOT NULL REFERENCES talabalar(id) ON DELETE CASCADE,
kurs_id INT NOT NULL REFERENCES kurslar(id) ON DELETE CASCADE,
PRIMARY KEY (talaba_id, kurs_id) -- ← bir xil juftlikni ikki marta qo'shib bo'lmaydi
);
Nima uchun PRIMARY KEY (talaba_id, kurs_id)? Bu composite primary key. Ali JavaScript ga faqat bir marta yozilishi mumkin — bu qoida shu orqali kafolatlanadi. Ikki marta qo'shishga harakat qilsa — xato beradi.
Nima uchun ON DELETE CASCADE? Talaba o'chirilsa — uning barcha kurs yozuvlari ham o'chishi kerak. Kurs o'chirilsa — unga yozilgan barcha talabalar ham junction table dan o'chishi kerak.
Ma'lumot qo'shish
INSERT INTO talabalar (ism) VALUES ('Ali'), ('Vali'), ('Soli');
INSERT INTO kurslar (nomi) VALUES ('JavaScript'), ('React'), ('Node.js'), ('Python');
-- Ali uchta kursga yozildi
INSERT INTO talaba_kurs VALUES (1, 1), (1, 2), (1, 3);
-- Vali ikkita kursga yozildi
INSERT INTO talaba_kurs VALUES (2, 4), (2, 1);
-- Soli bitta kursga yozildi
INSERT INTO talaba_kurs VALUES (3, 2);
Asosiy so'rovlar
Talabaning barcha kurslari
Ali qaysi kurslarga yozilgan?
SELECT k.nomi
FROM kurslar k
JOIN talaba_kurs tk ON k.id = tk.kurs_id
WHERE tk.talaba_id = 1;
nomi
------------
JavaScript
React
Node.js
Nima bo'ldi bu so'rovda:
talabalar talaba_kurs kurslar
Ali ──► (1,1) ──► JavaScript
(1,2) ──► React
(1,3) ──► Node.js
talaba_kurs junction table ko'prik vazifasini o'tamoqda.
Kursning barcha talabalari
JavaScript kursida kim bor?
SELECT t.ism
FROM talabalar t
JOIN talaba_kurs tk ON t.id = tk.talaba_id
WHERE tk.kurs_id = 1;
ism
------
Ali
Vali
Har bir kursda nechta talaba bor?
SELECT
k.nomi,
COUNT(tk.talaba_id) AS talabalar_soni
FROM kurslar k
LEFT JOIN talaba_kurs tk ON k.id = tk.kurs_id
GROUP BY k.id, k.nomi
ORDER BY talabalar_soni DESC;
nomi │ talabalar_soni
-------------+----------------
JavaScript │ 2
React │ 2
Node.js │ 1
Python │ 1
LEFT JOIN ishlatildi — chunki hech kim yozilmagan kurslar ham ko'rinishi kerak bo'lsa (COUNT = 0).
Bir xil kursni o'rganayotgan talabalarni topish
Ali bilan bir xil kurslarda kim o'qiyapti?
SELECT DISTINCT t.ism
FROM talabalar t
JOIN talaba_kurs tk ON t.id = tk.talaba_id
JOIN talaba_kurs ali ON ali.kurs_id = tk.kurs_id
WHERE ali.talaba_id = 1
AND t.id != 1; -- ← Alining o'zini chiqarmaymiz
ism
------
Vali
Soli
Bu so'rovda junction table ikki marta ishlatildi — tk (boshqa talabalar uchun) va ali (Ali uchun). Ularning kurs_id si mos keladigan talabalar — Ali bilan bir xil kursda.
Junction table ga qo'shimcha ustunlar
Junction table faqat IDlar saqlashi shart emas. U bog'lanishning o'ziga oid ma'lumotlarni ham saqlashi mumkin.
Talaba kursga qachon yozildi? Bahosi qancha?
CREATE TABLE talaba_kurs (
talaba_id INT NOT NULL REFERENCES talabalar(id) ON DELETE CASCADE,
kurs_id INT NOT NULL REFERENCES kurslar(id) ON DELETE CASCADE,
yozilgan_kun DATE NOT NULL DEFAULT CURRENT_DATE, -- ← qachon yozildi
baho INT CHECK (baho BETWEEN 1 AND 100), -- ← yakuniy baho
PRIMARY KEY (talaba_id, kurs_id)
);
Bu qo'shimcha ustunlar junction table ni oddiy bog'lovchi jadvaldan ma'lumotga boy jadvalga aylantiradi.
Tez-tez uchraydigan xatolar
Junction table da primary key yo'qligi:
-- ❌ — Ali JavaScript ga ikki marta yozilishi mumkin
CREATE TABLE talaba_kurs (
talaba_id INT REFERENCES talabalar(id),
kurs_id INT REFERENCES kurslar(id)
);
-- ✅ — composite primary key dublikatdan himoya qiladi
PRIMARY KEY (talaba_id, kurs_id)
Primary key yo'q bo'lsa — bir xil talaba bir xil kursga ikki marta yoziladi. Ma'lumot buziladi.
Ikkala tomonda ham JOIN yozishni unutish:
-- ❌ — junction table orqali o'tishni unutish
SELECT t.ism, k.nomi
FROM talabalar t
JOIN kurslar k ON t.id = k.id; -- ← bu noto'g'ri, junction table ishlatilmadi
-- ✅ — ikkita JOIN: avval junction table, keyin ikkinchi jadval
SELECT t.ism, k.nomi
FROM talabalar t
JOIN talaba_kurs tk ON t.id = tk.talaba_id
JOIN kurslar k ON k.id = tk.kurs_id;
Many-to-Many da har doim ikki JOIN: birinchi — junction table ga, ikkinchi — maqsad jadvalga.
Xulosa
Many-to-Many ni to'g'ridan-to'g'ri ikki jadval orasida ifodalab bo'lmaydi — buning uchun junction table kerak.
[Jadval A] ──► [Junction Table] ◄── [Jadval B]
talaba_id | kurs_id
Junction table — ikki foreign key + composite primary key. Bu uchlik — asosiy tuzilma.
Esda tuting:
- Composite
PRIMARY KEY (a_id, b_id)— dublikatdan himoya ON DELETE CASCADE— ota o'chirilsa bog'lanish ham o'chadi- Har doim ikki JOIN — avval junction, keyin maqsad jadval
- Junction table ga qo'shimcha ustunlar qo'shish mumkin — yozilgan sana, baho, holat
Keyingi dars
15-dars: One-to-One — qachon kerak