Many-to-Many — junction table, so'rovlar

~20 daq · PostgreSQL · JOIN va Munosabatlar

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