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.
Ushbu bo‘lim mundarijasi
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? #
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);
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;
+-----------------+------------------+------+
| muallif | sarlavha | yil |
+-----------------+------------------+------+
| Abdulla Qodiriy | O'tkan kunlar | 1926 |
| Abdulla Qodiriy | Mehrobdan chayon | 1929 |
| Cho'lpon | Kecha va kunduz | 1936 |
+-----------------+------------------+------+
Kitobsiz muallifni ko'rish #
-- 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;
+-----------------+---------------+
| ism | kitoblar_soni |
+-----------------+---------------+
| Abdulla Qodiriy | 2 |
| Cho'lpon | 1 |
| Erkin Vohidov | 0 |
+-----------------+---------------+
COUNT(*) va COUNT(ustun) farqiCOUNT(*) -- 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.
-- 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 #
-- muallif_id NOT NULL - kitob albatta muallifga tegishli
INSERT INTO kitoblar (sarlavha, muallif_id, yil) VALUES ('Nomsiz', NULL, 2000);
ERROR 1048 (23000): Column 'muallif_id' cannot be null
-- Mavjud bo'lmagan muallifga ishora ham mumkin emas
INSERT INTO kitoblar (sarlavha, muallif_id, yil) VALUES ('Sinov', 999, 2000);
ERROR 1452 (23000): Cannot add or update a child row:
a foreign key constraint fails
-- 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;
+------------+---------------+
| ustun | null_mumkinmi |
+------------+---------------+
| id | NO |
| sarlavha | NO |
| muallif_id | NO |
| yil | YES |
+------------+---------------+
NOT NULL tashqi kalitda - kuchli qoida| Holat | muallif_id | Ma'nosi |
|---|---|---|
NOT NULL | Majburiy | Muallifsiz kitob bo'lmaydi |
NULL mumkin | Ixtiyoriy | Muallif keyin tayinlanishi mumkin |
Ikkalasi ham to'g'ri bo'lishi mumkin - bu biznes qoidasiga bog'liq:
-- 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 #
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;
+-----------------+-------------------------+
| email | tavsif |
+-----------------+-------------------------+
| [email protected] | Dasturchi, Namangan |
| [email protected] | (profil to'ldirilmagan) |
+-----------------+-------------------------+
PRIMARY KEY ni FOREIGN KEY qilingBir-ga-bir bog'lanishning siri: bola jadvalning birlamchi kaliti ayni paytda tashqi kalit bo'ladi.
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? #
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
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
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 #
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;
+------------+-----------+
| bolim | ota |
+------------+-----------+
| Kompaniya | (ildiz) |
| IT | Kompaniya |
| Dasturlash | IT |
| Test | IT |
| Moliya | Kompaniya |
+------------+-----------+
Ierarxiyaning barcha darajasini bitta so'rovda olish uchun rekursiv CTE ishlatiladi:
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:
| Model | Afzalligi | Kamchiligi |
|---|---|---|
| Ota ko'rsatkichi | Oddiy, o'zgartirish oson | O'qish rekursiv |
Yo'l ro'yxati (/1/2/5/) | O'qish juda tez | Ko'chirish qimmat |
| Ichma-ich to'plam | Oralig'ni tez oladi | Qo'shish qimmat |
Ko'p loyihada ota ko'rsatkichi yetarli.
Bog'lanishni tekshirish #
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;
+------------+----------+------------+----------+
| mualliflar | kitoblar | kitobi_bor | kitobsiz |
+------------+----------+------------+----------+
| 3 | 3 | 2 | 1 |
+------------+----------+------------+----------+
- Muallif va kitob jadvallarini 1:N bog'lang.
- Tashqi kalit nima uchun "ko'p" tomonda turishini tushuntiring.
LEFT JOINbilan kitobsiz muallifni toping.COUNT(*)vaCOUNT(ustun)farqini ko'rsating.NULLva mavjud bo'lmaganidni kiritishga urinib ko'ring.- Bir-ga-bir bog'lanishni
PRIMARY KEY+FOREIGN KEYbilan quring. - Bir-ga-bir kerak bo'ladigan uch holatni sanang.
- O'z-o'ziga bog'langan bo'limlar daraxtini yarating.
- Rekursiv CTE bilan butun daraxtni chiqaring.
- Bog'lanish statistikasini bitta so'rovda oling.
Xulosa #
- Tashqi kalit har doim "ko'p" tomonda turadi.
LEFT JOINdan keyinCOUNT(ustun)ishlating,COUNT(*)emas.NOT NULLtashqi kalit bog'lanishni majburiy qiladi.NULLruxsat 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.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.