4-bo‘lim

Bir-ga-ko'p va bir-ga-bir

Eng ko'p uchraydigan bog'lanish turi, tashqi kalit qaysi tomonda turishi, bir-ga-bir qachon kerak.

🕑 14 daqiqa o‘qish 📄 588 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Tashqi kalit qaysi tomonda?
  2. Kitobsiz muallifni ko'rish
  3. Majburiy va ixtiyoriy bog'lanish
  4. Bir-ga-bir bog'lanish
  5. Bir-ga-bir qachon kerak?
  6. O'z-o'ziga bog'lanish
  7. Bog'lanishni tekshirish
  8. Xulosa

Bir-ga-ko'p (1:N) - sxemalardagi eng ko'p uchraydigan bog'lanish. Uni to'g'ri qurish yarim ishni bajaradi.

Tashqi kalit qaysi tomonda? #

Tashqi kalit har doim "ko'p" tomonda turadi mualliflar id (PK) ism "BIR" tomon 1 N kitoblar id (PK) muallif_id (FK) "KO'P" tomon Teskarisi ishlamaydi mualliflar.kitob_id bo'lsa - muallif faqat bitta kitob yoza oladi
Qoida: "ko'p" tomondagi jadval "bir" tomonga ishora qiladi
SQL
CREATE TABLE mualliflar (
    id       INT PRIMARY KEY AUTO_INCREMENT,
    ism      VARCHAR(100) NOT NULL,
    mamlakat VARCHAR(50)
);

CREATE TABLE kitoblar (
    id         INT PRIMARY KEY AUTO_INCREMENT,
    sarlavha   VARCHAR(200) NOT NULL,
    muallif_id INT NOT NULL,
    yil        SMALLINT,
    FOREIGN KEY (muallif_id) REFERENCES mualliflar(id)
);

INSERT INTO mualliflar (ism, mamlakat) VALUES
    ('Abdulla Qodiriy', 'O''zbekiston'),
    ('Cho''lpon',       'O''zbekiston'),
    ('Erkin Vohidov',   'O''zbekiston');

INSERT INTO kitoblar (sarlavha, muallif_id, yil) VALUES
    ('O''tkan kunlar',   1, 1926),
    ('Mehrobdan chayon', 1, 1929),
    ('Kecha va kunduz',  2, 1936);
SQL
SELECT m.ism AS muallif, k.sarlavha, k.yil
FROM kitoblar k
JOIN mualliflar m ON m.id = k.muallif_id
ORDER BY k.id;
Natija
+-----------------+------------------+------+
| muallif         | sarlavha         | yil  |
+-----------------+------------------+------+
| Abdulla Qodiriy | O'tkan kunlar    | 1926 |
| Abdulla Qodiriy | Mehrobdan chayon | 1929 |
| Cho'lpon        | Kecha va kunduz  | 1936 |
+-----------------+------------------+------+

Kitobsiz muallifni ko'rish #

SQL
-- INNER JOIN Erkin Vohidovni ko'rsatmaydi - uning kitobi yo'q
SELECT m.ism, COUNT(k.id) AS kitoblar_soni
FROM mualliflar m
LEFT JOIN kitoblar k ON k.muallif_id = m.id
GROUP BY m.id, m.ism
ORDER BY m.id;
Natija
+-----------------+---------------+
| ism             | kitoblar_soni |
+-----------------+---------------+
| Abdulla Qodiriy |             2 |
| Cho'lpon        |             1 |
| Erkin Vohidov   |             0 |
+-----------------+---------------+
COUNT(*) va COUNT(ustun) farqi
SQL
COUNT(*)      -- barcha qatorlarni sanaydi, NULL ham
COUNT(k.id)   -- faqat NULL bo'lmagan qiymatlarni sanaydi

LEFT JOIN da mos yozuv topilmasa, o'ng jadval ustunlari NULL bo'ladi. COUNT(*) bu qatorni ham sanaydi va Erkin Vohidov uchun 1 beradi - bu noto'g'ri.

SQL
-- NOTO'G'RI - kitobsiz muallif ham 1 ko'rsatadi
SELECT m.ism, COUNT(*) FROM mualliflar m
LEFT JOIN kitoblar k ON k.muallif_id = m.id GROUP BY m.id;

-- TO'G'RI
SELECT m.ism, COUNT(k.id) FROM mualliflar m
LEFT JOIN kitoblar k ON k.muallif_id = m.id GROUP BY m.id;

Qoida: LEFT JOIN dan keyin o'ng jadvalning ustunini sanang, * ni emas.

Majburiy va ixtiyoriy bog'lanish #

SQL
-- muallif_id NOT NULL - kitob albatta muallifga tegishli
INSERT INTO kitoblar (sarlavha, muallif_id, yil) VALUES ('Nomsiz', NULL, 2000);
Natija
ERROR 1048 (23000): Column 'muallif_id' cannot be null
SQL
-- Mavjud bo'lmagan muallifga ishora ham mumkin emas
INSERT INTO kitoblar (sarlavha, muallif_id, yil) VALUES ('Sinov', 999, 2000);
Natija
ERROR 1452 (23000): Cannot add or update a child row:
a foreign key constraint fails
SQL
-- Ikkala cheklov ham sxemada ko'rinadi
SELECT COLUMN_NAME AS ustun, IS_NULLABLE AS null_mumkinmi
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'kitoblar'
ORDER BY ORDINAL_POSITION;
Natija
+------------+---------------+
| ustun      | null_mumkinmi |
+------------+---------------+
| id         | NO            |
| sarlavha   | NO            |
| muallif_id | NO            |
| yil        | YES           |
+------------+---------------+
NOT NULL tashqi kalitda - kuchli qoida
Holatmuallif_idMa'nosi
NOT NULLMajburiyMuallifsiz kitob bo'lmaydi
NULL mumkinIxtiyoriyMuallif keyin tayinlanishi mumkin

Ikkalasi ham to'g'ri bo'lishi mumkin - bu biznes qoidasiga bog'liq:

SQL
-- Izoh maqolasiz bo'lmaydi
maqola_id INT NOT NULL

-- Buyurtma ro'yxatdan o'tmagan mehmondan ham kelishi mumkin
mijoz_id INT NULL

Muhim: NULL ruxsat berilgan tashqi kalit so'rovlarni murakkablashtiradi - har joyda LEFT JOIN va IS NULL tekshiruvi kerak bo'ladi.

Shuning uchun imkon bo'lsa NOT NULL qiling. Haqiqatan ixtiyoriy bo'lsa, buni ongli tanlang.

Bir-ga-bir bog'lanish #

SQL
CREATE TABLE foydalanuvchilar (
    id     INT PRIMARY KEY AUTO_INCREMENT,
    email  VARCHAR(100) NOT NULL UNIQUE,
    parol  CHAR(60)     NOT NULL
);

CREATE TABLE profillar (
    foydalanuvchi_id INT PRIMARY KEY,
    tavsif           TEXT,
    tugilgan_sana    DATE,
    avatar_url       VARCHAR(255),
    FOREIGN KEY (foydalanuvchi_id) REFERENCES foydalanuvchilar(id)
        ON DELETE CASCADE
);

INSERT INTO foydalanuvchilar (email, parol) VALUES
    ('[email protected]', 'xesh1'), ('[email protected]', 'xesh2');

INSERT INTO profillar (foydalanuvchi_id, tavsif) VALUES
    (1, 'Dasturchi, Namangan');

SELECT f.email, IFNULL(p.tavsif, '(profil to''ldirilmagan)') AS tavsif
FROM foydalanuvchilar f
LEFT JOIN profillar p ON p.foydalanuvchi_id = f.id
ORDER BY f.id;
Natija
+-----------------+-------------------------+
| email           | tavsif                  |
+-----------------+-------------------------+
| [email protected] | Dasturchi, Namangan     |
| [email protected]   | (profil to'ldirilmagan) |
+-----------------+-------------------------+
Bir-ga-bir - PRIMARY KEY ni FOREIGN KEY qiling

Bir-ga-bir bog'lanishning siri: bola jadvalning birlamchi kaliti ayni paytda tashqi kalit bo'ladi.

SQL
foydalanuvchi_id INT PRIMARY KEY,
FOREIGN KEY (foydalanuvchi_id) REFERENCES foydalanuvchilar(id)

Bu ikki narsani bir yo'la kafolatlaydi:

  • Har foydalanuvchida ko'pi bilan bitta profil (PRIMARY KEY);
  • Profil albatta mavjud foydalanuvchiga tegishli (FOREIGN KEY).

Alohida id qo'shib, keyin UNIQUE(foydalanuvchi_id) yozish ham ishlaydi, lekin ortiqcha ustun va indeks hosil qiladi.

Bir-ga-bir qachon kerak? #

Uch asosli holat

Ko'pincha bir-ga-bir keraksiz - ustunlarni bitta jadvalga qo'shsangiz bo'ladi. Lekin uch holatda u o'rinli:

1. Kamdan-kam ishlatiladigan katta ustunlar

SQL
foydalanuvchilar  -- har so'rovda o'qiladi, kichik
profillar         -- kamdan-kam kerak, TEXT va BLOB bor

InnoDB qatorni bo'laklab o'qiydi, shuning uchun katta TEXT ustunlar asosiy jadvalni sekinlashtiradi.

2. Turli huquqlar

SQL
xodimlar        -- hamma o'qiy oladi
xodim_maoshlari -- faqat hisobot bo'limi

Jadval darajasida huquq berish oson, ustun darajasida qiyin.

3. Ixtiyoriy ustunlar guruhi

Agar 10 ta ustun faqat foydalanuvchilarning 5% ida to'ldirilgan bo'lsa, ular asosiy jadvalda NULL bilan joy egallaydi.

Aks holda - ustunlarni bitta jadvalga qo'ying. Har JOIN qo'shimcha ish va murakkablik demakdir.

O'z-o'ziga bog'lanish #

SQL
CREATE TABLE bolimlar (
    id        INT PRIMARY KEY AUTO_INCREMENT,
    nom       VARCHAR(100) NOT NULL,
    ota_id    INT NULL,
    FOREIGN KEY (ota_id) REFERENCES bolimlar(id)
);

INSERT INTO bolimlar (nom, ota_id) VALUES
    ('Kompaniya',      NULL),
    ('IT',             1),
    ('Dasturlash',     2),
    ('Test',           2),
    ('Moliya',         1);

SELECT b.nom AS bolim, IFNULL(o.nom, '(ildiz)') AS ota
FROM bolimlar b
LEFT JOIN bolimlar o ON o.id = b.ota_id
ORDER BY b.id;
Natija
+------------+-----------+
| bolim      | ota       |
+------------+-----------+
| Kompaniya  | (ildiz)   |
| IT         | Kompaniya |
| Dasturlash | IT        |
| Test       | IT        |
| Moliya     | Kompaniya |
+------------+-----------+
Daraxtni rekursiv so'rov bilan o'qish

Ierarxiyaning barcha darajasini bitta so'rovda olish uchun rekursiv CTE ishlatiladi:

SQL
WITH RECURSIVE daraxt AS (
    SELECT id, nom, ota_id, 0 AS daraja
    FROM bolimlar WHERE ota_id IS NULL

    UNION ALL

    SELECT b.id, b.nom, b.ota_id, d.daraja + 1
    FROM bolimlar b
    JOIN daraxt d ON d.id = b.ota_id
)
SELECT REPEAT('  ', daraja) || nom AS tuzilma FROM daraxt;

Bu MariaDB 10.2+, MySQL 8+ va PostgreSQL da ishlaydi.

Juda chuqur yoki tez-tez o'qiladigan daraxt uchun boshqa modellar ham bor:

ModelAfzalligiKamchiligi
Ota ko'rsatkichiOddiy, o'zgartirish osonO'qish rekursiv
Yo'l ro'yxati (/1/2/5/)O'qish juda tezKo'chirish qimmat
Ichma-ich to'plamOralig'ni tez oladiQo'shish qimmat

Ko'p loyihada ota ko'rsatkichi yetarli.

Bog'lanishni tekshirish #

SQL
SELECT
    (SELECT COUNT(*) FROM mualliflar) AS mualliflar,
    (SELECT COUNT(*) FROM kitoblar)   AS kitoblar,
    (SELECT COUNT(DISTINCT muallif_id) FROM kitoblar) AS kitobi_bor,
    (SELECT COUNT(*) FROM mualliflar m
     WHERE NOT EXISTS (SELECT 1 FROM kitoblar k WHERE k.muallif_id = m.id))
        AS kitobsiz;
Natija
+------------+----------+------------+----------+
| mualliflar | kitoblar | kitobi_bor | kitobsiz |
+------------+----------+------------+----------+
|          3 |        3 |          2 |        1 |
+------------+----------+------------+----------+
Amaliy topshiriq
  1. Muallif va kitob jadvallarini 1:N bog'lang.
  2. Tashqi kalit nima uchun "ko'p" tomonda turishini tushuntiring.
  3. LEFT JOIN bilan kitobsiz muallifni toping.
  4. COUNT(*) va COUNT(ustun) farqini ko'rsating.
  5. NULL va mavjud bo'lmagan id ni kiritishga urinib ko'ring.
  6. Bir-ga-bir bog'lanishni PRIMARY KEY + FOREIGN KEY bilan quring.
  7. Bir-ga-bir kerak bo'ladigan uch holatni sanang.
  8. O'z-o'ziga bog'langan bo'limlar daraxtini yarating.
  9. Rekursiv CTE bilan butun daraxtni chiqaring.
  10. Bog'lanish statistikasini bitta so'rovda oling.

Xulosa #

  • Tashqi kalit har doim "ko'p" tomonda turadi.
  • LEFT JOIN dan keyin COUNT(ustun) ishlating, COUNT(*) emas.
  • NOT NULL tashqi kalit bog'lanishni majburiy qiladi.
  • NULL ruxsat etilgan kalit so'rovlarni murakkablashtiradi.
  • Bir-ga-birda bola jadvalning PRIMARY KEY = FOREIGN KEY.
  • Bir-ga-bir faqat uch holatda o'rinli: katta ustunlar, turli huquqlar, ixtiyoriy guruh.
  • Aks holda ustunlarni bitta jadvalga qo'ying.
  • O'z-o'ziga bog'lanish ierarxiyani ifodalaydi.
  • Daraxtni rekursiv CTE bilan o'qing.

Keyingi bo'limda ko'p-ga-ko'p bog'lanishni ko'ramiz.

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.