5-bo‘lim

Ko'p-ga-ko'p bog'lanish

Bog'lovchi jadval, kompozit kalit, bog'lanishning o'z atributlari va uch tomonlama bog'lanishlar.

🕑 13 daqiqa o‘qish 📄 791 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Nima uchun to'g'ridan-to'g'ri bog'lab bo'lmaydi
  2. Bog'lovchi jadval
  3. Teskari tomondan
  4. Bog'lanishning o'z atributlari
  5. Vergul bilan ajratilgan ro'yxat - eng ko'p uchraydigan xato
  6. To'g'ri yechim bilan taqqoslash
  7. Uch tomonlama bog'lanish
  8. Xulosa

Ikki jadval o'zaro ko'p bog'langanda, ular orasiga uchinchi jadval qo'yiladi. Buni chetlab o'tishga urinish - keng tarqalgan xato.

Nima uchun to'g'ridan-to'g'ri bog'lab bo'lmaydi #

N:M ni bog'lovchi jadval hal qiladi Ishlamaydigan urinishlar talabalar.fan_id -> talaba faqat bitta fanga yozila oladi talabalar.fanlar = '1,3,7' -> JOIN qilib bo'lmaydi, 1NF buzilgan talabalar id (PK) ism yozilishlar talaba_id (FK) fan_id (FK) PK (talaba_id, fan_id) fanlar id (PK) nom 1:N N:1 Bitta N:M ikkita 1:N ga aylanadi
Bog'lovchi jadval - N:M ning yagona to'g'ri yechimi

Bog'lovchi jadval #

SQL
CREATE TABLE talabalar (
    id  INT PRIMARY KEY AUTO_INCREMENT,
    ism VARCHAR(100) NOT NULL
);

CREATE TABLE fanlar (
    id     INT PRIMARY KEY AUTO_INCREMENT,
    nom    VARCHAR(50) NOT NULL UNIQUE,
    kredit TINYINT NOT NULL
);

CREATE TABLE yozilishlar (
    talaba_id INT NOT NULL,
    fan_id    INT NOT NULL,
    PRIMARY KEY (talaba_id, fan_id),
    FOREIGN KEY (talaba_id) REFERENCES talabalar(id) ON DELETE CASCADE,
    FOREIGN KEY (fan_id)    REFERENCES fanlar(id)    ON DELETE CASCADE
);

INSERT INTO talabalar (ism) VALUES ('Husanboy'), ('Malika'), ('Kamola');

INSERT INTO fanlar (nom, kredit) VALUES
    ('Matematika', 6), ('Fizika', 4), ('Ingliz tili', 3);

INSERT INTO yozilishlar (talaba_id, fan_id) VALUES
    (1, 1), (1, 2), (1, 3),
    (2, 1), (2, 3),
    (3, 2);
SQL
SELECT t.ism, GROUP_CONCAT(f.nom ORDER BY f.nom SEPARATOR ', ') AS fanlar
FROM talabalar t
JOIN yozilishlar y ON y.talaba_id = t.id
JOIN fanlar f      ON f.id = y.fan_id
GROUP BY t.id, t.ism
ORDER BY t.id;
Natija
+----------+---------------------------------+
| ism      | fanlar                          |
+----------+---------------------------------+
| Husanboy | Fizika, Ingliz tili, Matematika |
| Malika   | Ingliz tili, Matematika         |
| Kamola   | Fizika                          |
+----------+---------------------------------+

Teskari tomondan #

SQL
SELECT f.nom AS fan, COUNT(y.talaba_id) AS talabalar_soni
FROM fanlar f
LEFT JOIN yozilishlar y ON y.fan_id = f.id
GROUP BY f.id, f.nom
ORDER BY talabalar_soni DESC, f.nom;
Natija
+-------------+----------------+
| fan         | talabalar_soni |
+-------------+----------------+
| Fizika      |              2 |
| Ingliz tili |              2 |
| Matematika  |              2 |
+-------------+----------------+
Kompozit birlamchi kalit takrorlanishni to'xtatadi
SQL
PRIMARY KEY (talaba_id, fan_id)

Bu bitta qatorda ikki ish qiladi:

  1. Har juftlik bir marta yoziladi - talaba bir fanga ikki marta yozila olmaydi;
  2. Indeks yaratadi - talaba_id bo'yicha qidiruv tez.

Takrorlashga urinib ko'ring:

SQL
INSERT INTO yozilishlar VALUES (1, 1);
-- ERROR 1062: Duplicate entry '1-1' for key 'PRIMARY'

Bu qoida kodda emas, sxemada turibdi - hech qanday dastur uni buza olmaydi.

Ustunlar tartibi indeks uchun muhim: (talaba_id, fan_id) indeksi talaba_id bo'yicha qidiruvni tezlashtiradi, lekin faqat fan_id bo'yicha qidiruvni tezlashtirmaydi.

Ikkala yo'nalish ham kerak bo'lsa, ikkinchi indeks qo'shing:

SQL
CREATE INDEX idx_yozilishlar_fan ON yozilishlar (fan_id);

Buni 14 va 15-bo'limlarda batafsil ko'ramiz.

Bog'lanishning o'z atributlari #

Ko'pincha bog'lanishning o'z ma'lumoti bo'ladi:

SQL
CREATE TABLE yozilishlar_toliq (
    talaba_id   INT NOT NULL,
    fan_id      INT NOT NULL,
    yozilgan    DATE NOT NULL,
    baho        TINYINT NULL,
    holat       ENUM('faol', 'tugatgan', 'tashlagan') NOT NULL DEFAULT 'faol',
    PRIMARY KEY (talaba_id, fan_id),
    FOREIGN KEY (talaba_id) REFERENCES talabalar(id),
    FOREIGN KEY (fan_id)    REFERENCES fanlar(id)
);

INSERT INTO yozilishlar_toliq (talaba_id, fan_id, yozilgan, baho, holat) VALUES
    (1, 1, '2026-09-01', 5,    'tugatgan'),
    (1, 2, '2026-09-01', NULL, 'faol'),
    (2, 1, '2026-09-02', 4,    'tugatgan'),
    (3, 2, '2026-09-01', NULL, 'tashlagan');

SELECT t.ism, f.nom AS fan, y.holat, IFNULL(y.baho, 0) AS baho
FROM yozilishlar_toliq y
JOIN talabalar t ON t.id = y.talaba_id
JOIN fanlar f    ON f.id = y.fan_id
ORDER BY t.id, f.id;
Natija
+----------+------------+-----------+------+
| ism      | fan        | holat     | baho |
+----------+------------+-----------+------+
| Husanboy | Matematika | tugatgan  |    5 |
| Husanboy | Fizika     | faol      |    0 |
| Malika   | Matematika | tugatgan  |    4 |
| Kamola   | Fizika     | tashlagan |    0 |
+----------+------------+-----------+------+
Bog'lovchi jadval ham mavjudlikka aylanadi

Boshida yozilishlar shunchaki ikki id edi. Atributlar qo'shilgach, u o'z mavjudligiga aylandi:

BoshidaKeyin
Bog'lanishMavjudlik
(talaba_id, fan_id)yozilgan, baho, holat
"Kim nimaga yozilgan""Yozilish"

Bu tabiiy evolyutsiya. ER modelda buni assotsiativ mavjudlik deb atashadi.

Amaliy natija: agar bog'lovchi jadvalga boshqa jadval murojaat qiladigan bo'lsa (masalan davomat jadvali yozilishga ishora qilsa), unga sun'iy id qo'shing:

SQL
CREATE TABLE yozilishlar (
    id        INT PRIMARY KEY AUTO_INCREMENT,
    talaba_id INT NOT NULL,
    fan_id    INT NOT NULL,
    UNIQUE (talaba_id, fan_id),
    ...
);

UNIQUE qoidani saqlaydi, id esa murojaatni osonlashtiradi.

Vergul bilan ajratilgan ro'yxat - eng ko'p uchraydigan xato #

SQL
CREATE TABLE talabalar_yomon (
    id     INT PRIMARY KEY,
    ism    VARCHAR(100),
    fanlar VARCHAR(255)          -- '1,3,7' ko'rinishida
);

INSERT INTO talabalar_yomon VALUES
    (1, 'Husanboy', '1,2,3'),
    (2, 'Malika', '1,3'),
    (3, 'Kamola', '2');

-- "Matematika ga yozilgan talabalarni top" - qanday yozamiz?
SELECT ism, fanlar FROM talabalar_yomon WHERE FIND_IN_SET('1', fanlar) > 0;
Natija
+----------+--------+
| ism      | fanlar |
+----------+--------+
| Husanboy | 1,2,3  |
| Malika   | 1,3    |
+----------+--------+
Vergulli ro'yxat nima uchun yomon

Yuqoridagi so'rov ishlaydi, lekin bu yechim emas:

MuammoIzoh
Indeks ishlamaydiFIND_IN_SET butun jadvalni skanerlaydi
Tashqi kalit yo'q'1,99,3' - 99-fan mavjud emas, hech kim sezmaydi
JOIN qilib bo'lmaydiFan nomini olish uchun murakkab hiyla kerak
Yangilash qiyinBitta fanni olib tashlash - satrni qayta yig'ish
Atribut qo'shib bo'lmaydiBaho yoki sanani qayerga yozamiz?
Uzunlik chekloviVARCHAR(255) to'lib qolishi mumkin
1NF buzilgan6-bo'limda ko'ramiz

Million qatorli jadvalda FIND_IN_SET bo'yicha qidiruv soniyalar oladi, bog'lovchi jadvalda esa millisekundlar.

Xuddi shu narsa JSON massivi uchun ham to'g'ri:

SQL
fanlar JSON        -- [1, 2, 3]

JSON ustun ba'zan o'rinli (5-bo'lim oxiridagi eslatma), lekin u ham tashqi kalit va oddiy indeksdan mahrum.

Qoida: ro'yxat kerak bo'lsa - jadval yarating.

To'g'ri yechim bilan taqqoslash #

SQL
-- Bog'lovchi jadval bilan xuddi shu savol
SELECT t.ism
FROM talabalar t
JOIN yozilishlar y ON y.talaba_id = t.id
WHERE y.fan_id = 1
ORDER BY t.id;
Natija
+----------+
| ism      |
+----------+
| Husanboy |
| Malika   |
+----------+

Bog'lovchi jadvalda kerakli indekslar avtomatik hosil bo'ladi:

SQL
SELECT INDEX_NAME AS indeks, SEQ_IN_INDEX AS tartib, COLUMN_NAME AS ustun
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yozilishlar'
ORDER BY BINARY INDEX_NAME, SEQ_IN_INDEX;
Natija
+---------+--------+-----------+
| indeks  | tartib | ustun     |
+---------+--------+-----------+
| PRIMARY |      1 | talaba_id |
| PRIMARY |      2 | fan_id    |
| fan_id  |      1 | fan_id    |
+---------+--------+-----------+
Tashqi kalit indeksni o'zi yaratadi

PRIMARY indeksi (talaba_id, fan_id) - biz uni yozgan edik.

Lekin fan_id uchun alohida indeks paydo bo'ldi - biz uni so'ramagan edik.

Buni InnoDB o'zi qildi: har bir FOREIGN KEY uchun indeks majburiy, aks holda tashqi kalitni tekshirish har safar butun jadvalni skanerlashni talab qilardi.

Natijada bog'lovchi jadval ikkala yo'nalishda ham tez ishlaydi:

  • WHERE talaba_id = ?PRIMARY indeksi;
  • WHERE fan_id = ?fan_id indeksi.

PRIMARY ning ikkinchi ustuni bo'lgani uchun fan_id bo'yicha qidiruv yolg'iz PRIMARY bilan tezlashmasdi - shuning uchun bu qo'shimcha indeks bejiz emas.

Indekslar qanday ishlashini 14-bo'limda, EXPLAIN bilan tekshirishni 15-bo'limda ko'ramiz.

Uch tomonlama bog'lanish #

SQL
-- Talaba + fan + semestr: bir talaba bir fanni QAYTA o'qishi mumkin
CREATE TABLE semestrlar (
    id  INT PRIMARY KEY AUTO_INCREMENT,
    nom VARCHAR(20) NOT NULL UNIQUE
);

CREATE TABLE yozilishlar_semestr (
    talaba_id  INT NOT NULL,
    fan_id     INT NOT NULL,
    semestr_id INT NOT NULL,
    baho       TINYINT NULL,
    PRIMARY KEY (talaba_id, fan_id, semestr_id),
    FOREIGN KEY (talaba_id)  REFERENCES talabalar(id),
    FOREIGN KEY (fan_id)     REFERENCES fanlar(id),
    FOREIGN KEY (semestr_id) REFERENCES semestrlar(id)
);

INSERT INTO semestrlar (nom) VALUES ('2026-kuz'), ('2027-bahor');

INSERT INTO yozilishlar_semestr VALUES
    (1, 1, 1, 2),      -- yiqildi
    (1, 1, 2, 5);      -- qayta o'qib o'tdi

SELECT t.ism, f.nom AS fan, s.nom AS semestr, y.baho
FROM yozilishlar_semestr y
JOIN talabalar t  ON t.id = y.talaba_id
JOIN fanlar f     ON f.id = y.fan_id
JOIN semestrlar s ON s.id = y.semestr_id
ORDER BY s.id;
Natija
+----------+------------+------------+------+
| ism      | fan        | semestr    | baho |
+----------+------------+------------+------+
| Husanboy | Matematika | 2026-kuz   |    2 |
| Husanboy | Matematika | 2027-bahor |    5 |
+----------+------------+------------+------+
Kalitga ustun qo'shish - biznes qoidasini o'zgartiradi

E'tibor bering: (talaba_id, fan_id) kaliti bilan Husanboy matematikani qayta o'qiy olmasdi.

semestr_id qo'shilgach, qoida o'zgardi:

KalitQoida
(talaba_id, fan_id)Fanni bir marta o'qish mumkin
(talaba_id, fan_id, semestr_id)Har semestrda bir marta

Bu shunchaki texnik detal emas - bu biznes qarori. Uni buyurtmachi bilan aniqlashtiring:

"Talaba yiqilgan fanni qayta o'qiy oladimi?"

Javob "ha" bo'lsa, semestr kalitga kiradi. "Yo'q" bo'lsa - kirmaydi va sxema qayta o'qishni jismonan taqiqlaydi.

Bunday savollarni loyihalash paytida bering. Keyinchalik birlamchi kalitni o'zgartirish - eng qimmat migratsiyalardan biri.

Amaliy topshiriq
  1. Talaba va fanni bog'lovchi jadval bilan bog'lang.
  2. GROUP_CONCAT bilan har talabaning fanlarini chiqaring.
  3. Teskari tomondan - har fanning talabalarini sanang.
  4. Takrorlangan juftlik kiritishga urinib ko'ring.
  5. Bog'lovchi jadvalga baho va holat atributlarini qo'shing.
  6. Vergulli ro'yxat bilan saqlab, FIND_IN_SET bilan qidiring.
  7. Vergulli ro'yxatning oltita kamchiligini sanang.
  8. EXPLAIN bilan ikki yechimni taqqoslang.
  9. Uch tomonlama bog'lanish uchun jadval yarating.
  10. Kalitga ustun qo'shish qoidani qanday o'zgartirishini ayting.

Xulosa #

  • N:M bog'lanish bog'lovchi jadval talab qiladi.
  • Bitta N:M ikkita 1:N ga aylanadi.
  • Kompozit birlamchi kalit takrorlanishni to'xtatadi.
  • Indeks ustunlar tartibiga bog'liq - ikkinchi yo'nalish uchun qo'shimcha indeks kerak.
  • Bog'lanishga atribut qo'shilsa, u mavjudlikka aylanadi.
  • Unga boshqa jadval murojaat qilsa - sun'iy id qo'shing.
  • Vergulli ro'yxat indeks, tashqi kalit va JOIN dan mahrum qiladi.
  • Ro'yxat kerak bo'lsa - jadval yarating.
  • Kalitga ustun qo'shish biznes qoidasini o'zgartiradi.

Keyingi bo'limda normalizatsiyani - birinchi normal shakldan boshlaymiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.