Mundarija (26)
- Bu darsda
- 1. Nega bu kerak?
- 2. 1:N — eng ko'p uchraydigan bog'lanish
- 2.1 Qoida: tashqi kalit "ko'p" tomonda
- 2.2 Psql'da tekshiramiz
- 2.3 CASCADE va SET NULL amalda
- 3. N:M — ko'prik jadval
- 3.1 Nega to'g'ridan-to'g'ri bo'lmaydi
- 3.2 Tarkibiy kalit takrorni to'sadi
- 3.3 Kalit tartibi va ikkinchi indeks
- 3.4 Qo'shimcha maydonli ko'prik
- 4. 1:1 — birga-bir
- 4.1 Qachon kerak
- 4.2 PK = FK
- 4.3 Vaqtinchalik jadval tuzog'i
- 5. Self-relation — jadval o'ziga
- 6. Hamma bog'lanishlar — bitta so'rovda
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson)
- 2-mashq (o'rta)
- 3-mashq (qiyin)
- 9. SQL qadami
- 10. Real ishda
- Xulosa
- Manbalar
Bog'lanishlar 1:1, 1:N va N:M: diagrammadan jadvallarga
Qisqacha: 1:N da tashqi kalit "ko'p" tomondagi jadvalga yoziladi (
izohlar.vazifa_id). N:M ni to'g'ridan-to'g'ri ifodalab bo'lmaydi — ikki tashqi kalitli ko'prik (junction) jadval kerak, uning birlamchi kaliti ikkala ustundan iborat. 1:1 — tashqi kalit ustuni ayni paytda birlamchi kalit ham bo'lgan jadval. Self-relation — jadval o'ziga havola qiladi (ota_id).
Bu darsda
- Har bog'lanish turida tashqi kalit qaysi jadvalga yozilishini aniq bilasiz.
izohlarjadvalini (1:N) quramiz vaON DELETE CASCADEbilanSET NULLfarqini amalda ko'rasiz.- Ko'prik jadvalni tarkibiy kalit va qo'shimcha maydonlar bilan loyihalaysiz.
- 1:1 bog'lanishni ikki usulda yozasiz va qachon kerakligini bilasiz.
- Bazadagi hamma bog'lanishlarni katalogdan bitta so'rov bilan chiqarasiz.
Oldin bilishingiz kerak: ER modellash va diagrammalar, Foreign key va referential actions, UPSERT va MERGE, Rekursiv CTE.
1. Nega bu kerak?
O'tgan darsda diagrammada chiziq chizdik: vazifalar ||--o{ izohlar. Endi uni SQL'ga aylantirish kerak. Savol oddiy ko'rinadi: tashqi kalitni qayerga yozamiz — vazifalar ga izoh_id mi yoki izohlar ga vazifa_id mi?
Noto'g'ri tanlov qimmatga tushadi. vazifalar.izoh_id desak, vazifada faqat bitta izoh bo'ladi. Ikkinchisini qo'shish uchun izoh2_id, izoh3_id ustunlari kerak bo'ladi — bu yo'lning oxiri yo'q.
O'xshatish: maktab sinfi. Sinf jurnaliga 30 o'quvchining ismini ustun qilib yozish mumkin emas — o'quvchi soni har yili boshqa. Buning o'rniga har o'quvchining kartochkasiga "sinfi: 9-B" deb yoziladi. "Ko'p" tomonda — havola, "bitta" tomonda — hech narsa. Bu qoida bog'lanishlarning asosi.
2. 1:N — eng ko'p uchraydigan bog'lanish
2.1 Qoida: tashqi kalit "ko'p" tomonda
Birga-ko'p (one-to-many, 1:N) — bitta yozuvga ko'p yozuv bog'lanadi: bitta vazifa — ko'p izoh. Tashqi kalit har doim "ko'p" tomondagi jadvalga yoziladi: izohlar.vazifa_id. Chunki har izohning vazifasi bitta — ustun ham bitta.
Kanon holat 34 da izohlar jadvali qo'shildi. Mana uning to'liq SQL fayli (kanon sql/007-izohlar.sql dan, aynan):
CREATE TABLE izohlar (
id bigint GENERATED ALWAYS AS IDENTITY,
vazifa_id bigint NOT NULL,
-- muallif o'chirilsa izoh qoladi ("o'chirilgan foydalanuvchi") — SET NULL; hozir muallif = vazifa egasi,
-- jamoaviy vazifalar — 38-qism capstone (`jamoa`)
muallif_id bigint,
matn text NOT NULL,
yaratilgan_vaqt timestamptz NOT NULL DEFAULT now(),
CONSTRAINT izohlar_pkey PRIMARY KEY (id),
CONSTRAINT izohlar_vazifa_id_fkey FOREIGN KEY (vazifa_id) REFERENCES vazifalar (id) ON DELETE CASCADE,
CONSTRAINT izohlar_muallif_id_fkey FOREIGN KEY (muallif_id) REFERENCES foydalanuvchilar (id) ON DELETE SET NULL,
CONSTRAINT izohlar_matn_check CHECK (btrim(matn) <> '' AND char_length(matn) <= 2000)
);
-- "vazifaning izohlari, vaqt tartibida" — bitta indeks ham FK'ni, ham saralashni qoplaydi (#57)
CREATE INDEX izohlar_vazifa_id_yaratilgan_vaqt_idx ON izohlar (vazifa_id, yaratilgan_vaqt);
CREATE INDEX izohlar_muallif_id_idx ON izohlar (muallif_id);Bu bitta jadvalda ikkita 1:N bog'lanish bor:
| Tashqi kalit | Bog'lanish | NOT NULL? |
O'chirishda |
|---|---|---|---|
vazifa_id |
vazifa → izohlar | ha — izoh vazifasiz bo'lmaydi | CASCADE |
muallif_id |
foydalanuvchi → izohlar | yo'q — muallif yo'qolishi mumkin | SET NULL |
Diagrammadagi belgilar shu yerdan keladi: NOT NULL — || (aniq bitta), NULL mumkin — |o (nol yoki bitta). Ikki indeks ham bejiz emas: Postgres tashqi kalit ustuniga indeksni o'zi yaratmaydi (Foreign key darsidagi qoida). Birinchi indeks (vazifa_id, yaratilgan_vaqt) — ham tashqi kalitni, ham "izohlar vaqt tartibida" so'rovini qoplaydi. Bu haqida indekslar tartibi darsida batafsil gaplashamiz.
Ikki kod qatori 70 belgidan uzun — kanon faylidan aynan olingan, qisqartirmadik. Izohdagi "38-qism capstone" — kursning keyingi qismidagi jamoaviy loyiha.
2.2 Psql'da tekshiramiz
Jadval tuzilishini \d ko'rsatadi (psql chuqur). Kanon chiqishi (PostgreSQL 18.6):
Table "public.izohlar"
Column | Type | Collation | Nullable | Default
-----------------+--------------------------+-----------+----------+------------------------------
id | bigint | | not null | generated always as identity
vazifa_id | bigint | | not null |
muallif_id | bigint | | |
matn | text | | not null |
yaratilgan_vaqt | timestamp with time zone | | not null | now()
Indexes:
"izohlar_pkey" PRIMARY KEY, btree (id)
"izohlar_muallif_id_idx" btree (muallif_id)
"izohlar_vazifa_id_yaratilgan_vaqt_idx" btree (vazifa_id, yaratilgan_vaqt)
Check constraints:
"izohlar_matn_check" CHECK (btrim(matn) <> ''::text AND char_length(matn) <= 2000)
Foreign-key constraints:
"izohlar_muallif_id_fkey" FOREIGN KEY (muallif_id) REFERENCES foydalanuvchilar(id) ON DELETE SET NULL
"izohlar_vazifa_id_fkey" FOREIGN KEY (vazifa_id) REFERENCES vazifalar(id) ON DELETE CASCADENullable ustunida muallif_id bo'sh — demak NULL bo'lishi mumkin. Pastda ikkala tashqi kalit o'z amali bilan.
Namuna ma'lumotda (kanon 008-namuna-izohlar.sql) 15 ta izoh bor. Har vazifada nechta izoh borligini sanaymiz — 1:N bog'lanishning "ko'p" tomonini LEFT JOIN va GROUP BY bilan yig'amiz:
SELECT v.matn, count(i.id) AS izohlar
FROM vazifalar v
LEFT JOIN izohlar i ON i.vazifa_id = v.id
GROUP BY v.id
HAVING count(i.id) > 0
ORDER BY 2 DESC, v.id; matn | izohlar
--------------------------------------------------+---------
JavaScript kursining 26-qismini boshlash #o'qish | 3
Server zaxirasini tekshirish #ish #server | 2
Mashinani texnik ko'rikka olib borish #mashina | 2
Oshxonadagi jo'mrakni almashtirish #ta'mir #uy | 2
Postgres'ni Docker'da ko'tarish #o'qish #docker | 1
Kurs ishi: ma'lumotlar bazasi bo'limi #o'qish | 1
Buyurtmalarni Telegram'da qabul qilish #ish | 1
IELTS writing task 2 mashqi #ingliz | 1
Universitet kontrakti #to'lov | 1
Soliq hisobotini topshirish #ish #to'lov | 1
(10 rows)O'nta vazifada izoh bor, 15 izoh jami. Qolgan vazifalarda 0 — HAVING ularni olib tashladi.
2.3 CASCADE va SET NULL amalda
Ikki tashqi kalitning amali turli. Kanon ularni tranzaksiyada sinab, oxirida ROLLBACK bilan qaytargan. Biz ikkala tajribani alohida tranzaksiyaga ajratdik — har biri o'zi to'liq. Avval izohni vaqtincha Shahnoza (id 8) nomiga o'tkazamiz va Shahnozani o'chiramiz:
BEGIN;
UPDATE izohlar SET muallif_id = 8 WHERE id = 1;
DELETE FROM foydalanuvchilar WHERE id = 8;
SELECT id, vazifa_id, muallif_id, matn FROM izohlar WHERE id = 1;
ROLLBACK;BEGIN
UPDATE 1
DELETE 1
id | vazifa_id | muallif_id | matn
----+-----------+------------+-------------------------------------
1 | 2 | | Port 5432 band ekan, 5499 ga o'tdim
(1 row)
ROLLBACKIzoh qoldi, muallif_id bo'sh — SET NULL ishladi. Ilova uni "o'chirilgan foydalanuvchi" deb ko'rsatadi. Endi vazifani o'chiramiz:
BEGIN;
DELETE FROM vazifalar WHERE id = 1;
SELECT count(*) AS vazifa1_izohlari FROM izohlar WHERE vazifa_id = 1;
ROLLBACK;BEGIN
DELETE 1
vazifa1_izohlari
------------------
0
(1 row)
ROLLBACK1-vazifaning uchta izohi ham ketdi — CASCADE. Vazifasiz izohning ma'nosi yo'q, shuning uchun bu to'g'ri tanlov.
Qaysi amalni tanlash — biznes savoli, texnik emas:
| Savol | Javob "ha" bo'lsa |
|---|---|
| Ota-onasiz bola ma'nosizmi? (vazifasiz izoh) | CASCADE |
| Bola qolishi kerak, lekin "egasiz"? (muallifsiz izoh) | SET NULL (ustun NULL bo'la olsin) |
| Bolasi bor ota-onani o'chirish taqiqmi? (to'lovi bor mijoz) | RESTRICT / NO ACTION |
Tekshirib ko'ring:
muallif_idgaNOT NULLqo'shsak,ON DELETE SET NULLbilan nima bo'ladi?
Javob
Foydalanuvchini o'chirishda Postgres muallif_id ni NULL qilishga urinadi va NOT NULL cheklovi bunga yo'l qo'ymaydi — DELETE xato bilan to'xtaydi. SET NULL faqat NULL bo'la oladigan ustunda ma'noga ega. Diagrammada ham shunday: |o (nol yoki bitta) va SET NULL doim birga keladi.
3. N:M — ko'prik jadval
3.1 Nega to'g'ridan-to'g'ri bo'lmaydi
Ko'pga-ko'p (many-to-many, N:M) — ikkala tomonda ham "ko'p": bitta vazifada ko'p teg, bitta teg ko'p vazifada. Tashqi kalitni qaysi tomonga yozsak ham, u faqat bitta qiymat saqlaydi — bu N:M emas, 1:N bo'lib qoladi.
Yechim — uchinchi jadval. Ko'prik jadval (junction table) — har qatori bitta juftlikni saqlaydi: "shu vazifa — shu teg". Bizda bu vazifa_teglari, uni UPSERT va MERGE darsida qurgan edik. Uning kaliti — tarkibiy birlamchi kalit (composite primary key), ya'ni ikki ustundan iborat kalit: (vazifa_id, teg_id).
Nimaga qarang: N:M bog'lanish aslida ikkita 1:N — har ikki tomon ko'prik jadvalga "bitta → ko'p" bilan ulanadi.
erDiagram
vazifalar ||--o{ vazifa_teglari : "vazifa_id"
teglar ||--o{ vazifa_teglari : "teg_id"
vazifa_teglari {
bigint vazifa_id PK, FK
bigint teg_id PK, FK
}Ko'prik orqali so'rov — ikki marta birlashtirish (JOIN). Biz o'z PostgreSQL 17.6 klasterimizda (holat 34) ishga tushirdik:
SELECT v.matn, string_agg(t.nom, ' ' ORDER BY t.nom) AS teglar
FROM vazifalar v
JOIN vazifa_teglari vt ON vt.vazifa_id = v.id
JOIN teglar t ON t.id = vt.teg_id
WHERE v.id IN (1, 2)
GROUP BY v.id
ORDER BY v.id; matn | teglar
--------------------------------------------------+-----------------
JavaScript kursining 26-qismini boshlash #o'qish | #o'qish
Postgres'ni Docker'da ko'tarish #o'qish #docker | #docker #o'qish
(2 rows)3.2 Tarkibiy kalit takrorni to'sadi
Bir juftlikni ikki marta yozish ma'nosiz: "1-vazifa — #o'qish" ikki marta bo'lsa, teglar soni noto'g'ri sanaladi. Tarkibiy birlamchi kalit buni bazada to'sadi. Kanon chiqishi:
INSERT INTO vazifa_teglari
VALUES (1, (SELECT teg_id FROM vazifa_teglari
WHERE vazifa_id = 1 LIMIT 1));ERROR: duplicate key value violates unique constraint "vazifa_teglari_pkey"
DETAIL: Key (vazifa_id, teg_id)=(1, 4) already exists.DETAIL qatoridagi teg raqami bazaning collation'iga bog'liq: 006-teglar.sql teglarni egasi va nomi bo'yicha alifbo tartibida raqamlaydi, alifbo tartibi esa collation'dan keladi. Kanon bazasi (en_US.utf8) va kanonga o'xshash tartibli bizning PostgreSQL 17.6 klasterimiz (1, 4) berdi; C collation'li bazada raqam (1, 3) bo'lgan. Xatoning o'zi bir xil.
3.3 Kalit tartibi va ikkinchi indeks
Tarkibiy kalit (vazifa_id, teg_id) — ayni paytda indeks. U "1-vazifaning teglari" so'rovini tez bajaradi, chunki birinchi ustun bo'yicha qidiradi. Teskari so'rov — "#o'qish tegli vazifalar" — ikkinchi ustundan boshlanadi va bu indeks yordam bermaydi. Shuning uchun kanon 006-teglar.sql da alohida teg_id indeksi bor. Ko'prik jadvalda odatda: tarkibiy PK + ikkinchi ustunga indeks.
3.4 Qo'shimcha maydonli ko'prik
Ko'prik jadval faqat ikki ustundan iborat bo'lishi shart emas. Juftlikning o'ziga tegishli ma'lumot ham shu yerga yoziladi:
- vazifaga teg qachon qo'yilgan;
- kursga yozilgan talabaning bahosi (talaba ↔ kurs);
- buyurtmadagi mahsulotning soni (buyurtma ↔ mahsulot).
Bunday jadval ko'pincha o'z nomini oladi: buyurtma_qatorlari, azoliklar. U endi "texnik ko'prik" emas, o'z ma'nosi bor entity. 3-mashqda shunday jadval quramiz.
Maslahat: Ko'prik jadvalga alohida
id(surrogat kalit) kerakmi? Agar boshqa jadval unga havola qilmasa — yo'q, tarkibiy kalit yetarli va takrorni o'zi to'sadi. Agar havola kerak bo'lsa (masalan, buyurtma qatoriga to'lov bog'lanadi) —idqo'shing, lekinUNIQUE (buyurtma_id, mahsulot_id)ni ham qoldiring.
Tekshirib ko'ring: Talabalar va kurslar N:M. Talaba bitta kursga ikki marta yozila olmasligi kerak. Buni qanday kafolatlaysiz?
Javob
Ko'prik jadval azoliklar (talaba_id, kurs_id, …) ning birlamchi kaliti (talaba_id, kurs_id) bo'lsin. Ikkinchi yozuv duplicate key value violates unique constraint bilan rad etiladi. Ilova kodidagi tekshiruv ("avval SELECT, keyin INSERT") yetmaydi — ikki so'rov bir vaqtda kelsa, ikkalasi ham "yo'q" deb ko'radi.
4. 1:1 — birga-bir
4.1 Qachon kerak
Birga-bir (one-to-one, 1:1) — har yozuvga ko'pi bilan bitta yozuv mos keladi. Ko'pincha savol tug'iladi: unda nega alohida jadval? Ustunlarni asosiy jadvalga qo'shsa bo'lmaydimi? Ko'p hollarda — bo'ladi va shunday qilish kerak. Alohida jadval faqat sababi bo'lsa:
- Ixtiyoriy qism: foydalanuvchilarning 5 foizida to'ldiriladigan 20 ta ustun (masalan, kompaniya rekvizitlari).
- Katta yoki kam o'qiladigan ma'lumot: rasm, uzun bio — asosiy jadval yengil qolsin.
- Boshqa ruxsatlar: maxfiy maydonlarni alohida jadvalda saqlab, kirishni cheklash.
4.2 PK = FK
Eng toza usul: bola jadvalning birlamchi kaliti ayni paytda tashqi kalit. Kanon buni vaqtinchalik t34_profil jadvalida sinagan (kanon sxemasida profil yo'q):
BEGIN;
CREATE TABLE t34_profil (
foydalanuvchi_id bigint PRIMARY KEY
REFERENCES foydalanuvchilar ON DELETE CASCADE,
telegram text,
tema text DEFAULT 'tizim'
);
INSERT INTO t34_profil VALUES (1, '@aziz_k');
INSERT INTO t34_profil VALUES (1, '@boshqa');BEGIN
CREATE TABLE
INSERT 0 1
ERROR: duplicate key value violates unique constraint "t34_profil_pkey"
DETAIL: Key (foydalanuvchi_id)=(1) already exists.Xatodan keyin tranzaksiya "buzilgan" holatda: undagi keyingi buyruqlar bajarilmaydi, faqat ROLLBACK; qabul qilinadi (ACID darsidagi current transaction is aborted). ROLLBACK; yozing — jadval ham, qator ham yo'qoladi. psql seansi yopilsa, Postgres ochiq tranzaksiyani o'zi bekor qiladi.
Ikkinchi profil rad etildi — bitta foydalanuvchiga bitta profil. Kalit va havola bitta ustunda, shuning uchun alohida id kerak emas.
Ikkinchi usul: bola jadvalda o'z id si va foydalanuvchi_id bigint UNIQUE REFERENCES …. Natija bir xil (UNIQUE ikkinchi qatorni to'sadi), lekin bitta ortiqcha ustun va indeks. Agar UNIQUE ni unutsangiz — jadval jim 1:N bo'lib qoladi.
4.3 Vaqtinchalik jadval tuzog'i
Kanon tajribaning birinchi urinishida t34_profil vaqtinchalik jadval (CREATE TEMP TABLE) qilib yaratilgan va yiqilgan. Biz ham o'z klasterimizda takrorladik:
CREATE TEMP TABLE t34_vaqtincha (
foydalanuvchi_id bigint REFERENCES foydalanuvchilar
);ERROR: constraints on temporary tables may reference only temporary tablesTarjimasi: "vaqtinchalik jadvallardagi cheklovlar faqat vaqtinchalik jadvallarga havola qila oladi". Sababi mantiqiy: vaqtinchalik jadval seans tugashi bilan yo'qoladi, doimiy jadval esa uning yozuvlarini kuzatib borolmaydi. Shuning uchun kanon oddiy jadval + BEGIN … ROLLBACK usuliga o'tgan.
5. Self-relation — jadval o'ziga
O'z-o'ziga bog'lanish (self-relation) — tashqi kalit o'sha jadvalga qaytadi. Bizda bu vazifalar.ota_id: kichik vazifa boshqa vazifaga tegishli. Uni ALTER TABLE darsida qo'shgan va Rekursiv CTE darsida daraxt sifatida o'qigan edik.
Jadval yangi narsa talab qilmaydi: bu oddiy 1:N, faqat "ota" va "bola" — bitta jadval. Ikki nozik joyni eslatamiz:
ota_idNULL bo'la oladi — aks holda birinchi vazifani yozib bo'lmasdi (unga ota kerak bo'lardi).- Bizda tashqi kalit tarkibiy:
(ota_id, egasi_id) → vazifalar (id, egasi_id). U oddiyota_id → iddan kuchliroq: Azizning vazifasi Dilnozaning vazifasiga "bola" bo'lib qololmaydi.
Boshqa misollar: xodim → rahbar (rahbar_id), izohga javob (javob_berilgan_id), kategoriya → ota kategoriya. Daraxtlarni saqlashning boshqa usullari ham bor — ularni Modellash naqshlari darsida solishtiramiz.
6. Hamma bog'lanishlar — bitta so'rovda
O'tgan darsda katalogdan tashqi kalitlar ro'yxatini oldik. Endi ON DELETE amalini ham so'z bilan chiqaramiz. Holat 34 da (biz ishga tushirdik, PG 17.6; kanon 18.6 chiqishi bir xil):
SELECT conrelid::regclass AS bola,
confrelid::regclass AS ota,
CASE confdeltype
WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL'
WHEN 'r' THEN 'RESTRICT' WHEN 'a' THEN 'NO ACTION'
END AS on_delete
FROM pg_constraint
WHERE contype = 'f'
ORDER BY 1, 2; bola | ota | on_delete
----------------+------------------+-----------
vazifalar | foydalanuvchilar | CASCADE
vazifalar | vazifalar | CASCADE
teglar | foydalanuvchilar | CASCADE
vazifa_teglari | vazifalar | CASCADE
vazifa_teglari | teglar | CASCADE
izohlar | foydalanuvchilar | SET NULL
izohlar | vazifalar | CASCADE
(7 rows)Yettita qator — yettita bog'lanish: to'rtta 1:N (foydalanuvchi→vazifa, foydalanuvchi→teg, vazifa→izoh, foydalanuvchi→izoh), bitta self-relation va N:M ning ikki oyog'i. Yagona SET NULL — muallif.
Bu so'rov katta bazani o'rganishda juda foydali: "foydalanuvchini o'chirsam, nima ketadi?" — CASCADE zanjirini kuzatib chiqasiz: foydalanuvchi → vazifalari → ularning izohlari va teglari.
7. Ko'p uchraydigan xatolar
1. Tashqi kalit "bitta" tomonda. vazifalar.izoh_id — vazifada bitta izoh. Belgisi: izoh1_id, izoh2_id kabi raqamli ustunlar yoki "nechta bo'lishi mumkin?" savoliga "hozircha bitta" javobi. Tuzatish: tashqi kalit "ko'p" tomonga.
2. N:M ni vergulli matnda saqlash. teglar = 'uy,ish,oila'. Qidiruv LIKE '%uy%' uyqu ni ham topadi, teg nomini o'zgartirish — hamma qatorni tahrirlash. Bu xatoni Normalizatsiya darsida o'lchov bilan ko'ramiz. Tuzatish: ko'prik jadval.
3. Ko'prik jadvalda kalitsiz. vazifa_teglari (vazifa_id, teg_id) — PK ham, UNIQUE ham yo'q. Bir juftlik bir necha marta yoziladi va hisoblar ikki baravar chiqadi. Tuzatish: tarkibiy PK.
4. 1:1 da UNIQUE'siz tashqi kalit. profillar.foydalanuvchi_id REFERENCES … — UNIQUE yo'q. Jadval jim 1:N bo'ladi: bitta foydalanuvchida ikki profil va ilova qaysi birini olishni bilmaydi. Tuzatish: PK = FK yoki UNIQUE.
5. Tashqi kalit ustuniga indeks yo'q. CASCADE bilan ota-onani o'chirishda Postgres bola jadvalni har safar to'liq ko'rib chiqadi. Tuzatish: har tashqi kalit ustuniga indeks — kanon review'da buni so'rov bilan tekshiramiz (Real loyiha sxemasi).
6. ON DELETE ni o'ylamasdan tanlash. Hamma joyda CASCADE — bitta DELETE minglab qatorni olib ketishi mumkin (to'lov tarixi ham). Har bog'lanish uchun «CASCADE va SET NULL amalda» bo'limidagi jadval savollarini bering.
8. Mashqlar
Mashqlar holat 34 da (namuna ma'lumot bilan). Jadval yaratadigan mashqlarni BEGIN … ROLLBACK ichida bajaring — kanon sxemasi o'zgarmasin.
1-mashq (oson)
Har foydalanuvchi nechta izoh yozganini chiqaring — izoh yozmaganlar ham 0 bilan ko'rinsin. Ustunlar: ism, izohlar; ko'pidan kamiga, teng bo'lsa id bo'yicha. Ishora: «Psql'da tekshiramiz» bo'limidagi sanash so'rovi, faqat bog'lanish muallif_id orqali.
Yechim
SELECT f.ism, count(i.id) AS izohlar
FROM foydalanuvchilar f
LEFT JOIN izohlar i ON i.muallif_id = f.id
GROUP BY f.id
ORDER BY izohlar DESC, f.id; ism | izohlar
--------------------+---------
Aziz Karimov | 6
Sardor Tursunov | 3
Javohir Olimov | 2
Dilnoza Rahimova | 1
Madina Yusupova | 1
Gulnora Saidova | 1
Bekzod Nazarov | 1
Shahnoza Ergasheva | 0
(8 rows)count(*) emas, count(i.id): LEFT JOIN Shahnoza uchun bitta "bo'sh" qator beradi va count(*) uni 1 deb sanardi. count(i.id) NULL'ni sanamaydi (NULL mantiqi).
2-mashq (o'rta)
1:1 bog'lanish: profillar jadvalini yarating — foydalanuvchi_id (PK va FK, foydalanuvchi o'chsa profil ham o'chsin), telegram, shahar. 1- va 2-foydalanuvchiga profil yozing, keyin 2-foydalanuvchini o'chiring va profillarni chiqaring. Hammasi BEGIN … ROLLBACK ichida. Ishora: «PK = FK» bo'limidagi t34_profil.
Yechim
BEGIN;
CREATE TABLE profillar (
foydalanuvchi_id bigint PRIMARY KEY
REFERENCES foydalanuvchilar ON DELETE CASCADE,
telegram text,
shahar text
);
INSERT INTO profillar VALUES (1, '@sizning_nik', 'Toshkent'),
(2, NULL, 'Samarqand');
DELETE FROM foydalanuvchilar WHERE id = 2;
SELECT * FROM profillar;
ROLLBACK;BEGIN
CREATE TABLE
INSERT 0 2
DELETE 1
foydalanuvchi_id | telegram | shahar
------------------+--------------+----------
1 | @sizning_nik | Toshkent
(1 row)
ROLLBACK2-foydalanuvchi o'chganda uning profili ham CASCADE bilan ketdi. DELETE 1 faqat foydalanuvchini sanaydi — kaskad bilan o'chgan bola qatorlar (profil, vazifalar, izohlar) bu songa kirmaydi.
3-mashq (qiyin)
Qo'shimcha maydonli N:M: vazifani boshqa foydalanuvchilar kuzatishi mumkin. vazifa_kuzatuvchilari jadvalini yarating: vazifa_id, foydalanuvchi_id (ikkalasi FK, CASCADE), qoshilgan_vaqt (sukut now()), tarkibiy PK, ikkinchi ustunga indeks. 1-vazifaga 2- va 3-foydalanuvchini, 5-vazifaga 2-foydalanuvchini qo'shing. Har vazifa uchun kuzatuvchilar sonini va ismlarini (vergul bilan, id tartibida) chiqaring. Oxirida (1, 2) juftini qayta qo'shib ko'ring. Hammasi BEGIN … ROLLBACK ichida. Ishora: string_agg(…, ', ' ORDER BY …) — Agregatlar.
Yechim
BEGIN;
CREATE TABLE vazifa_kuzatuvchilari (
vazifa_id bigint NOT NULL
REFERENCES vazifalar ON DELETE CASCADE,
foydalanuvchi_id bigint NOT NULL
REFERENCES foydalanuvchilar ON DELETE CASCADE,
qoshilgan_vaqt timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (vazifa_id, foydalanuvchi_id)
);
CREATE INDEX vazifa_kuzatuvchilari_foydalanuvchi_id_idx
ON vazifa_kuzatuvchilari (foydalanuvchi_id);
INSERT INTO vazifa_kuzatuvchilari (vazifa_id, foydalanuvchi_id)
VALUES (1, 2), (1, 3), (5, 2);
SELECT v.id, count(k.foydalanuvchi_id) AS kuzatuvchilar,
string_agg(f.ism, ', ' ORDER BY f.id) AS kimlar
FROM vazifalar v
JOIN vazifa_kuzatuvchilari k ON k.vazifa_id = v.id
JOIN foydalanuvchilar f ON f.id = k.foydalanuvchi_id
GROUP BY v.id
ORDER BY v.id;
INSERT INTO vazifa_kuzatuvchilari (vazifa_id, foydalanuvchi_id)
VALUES (1, 2);BEGIN
CREATE TABLE
CREATE INDEX
INSERT 0 3
id | kuzatuvchilar | kimlar
----+---------------+-----------------------------------
1 | 2 | Dilnoza Rahimova, Sardor Tursunov
5 | 1 | Dilnoza Rahimova
(2 rows)
ERROR: duplicate key value violates unique constraint "vazifa_kuzatuvchilari_pkey"
DETAIL: Key (vazifa_id, foydalanuvchi_id)=(1, 2) already exists.Oxirida ROLLBACK; yozing — xatodan keyin tranzaksiya faqat shuni qabul qiladi. qoshilgan_vaqt — juftlikning o'z ma'lumoti: u na vazifaga, na foydalanuvchiga tegishli. Ikkinchi indeks "Dilnoza nimalarni kuzatyapti?" so'rovi uchun. Takroriy juft tarkibiy kalit bilan rad etildi. Indeks nomi 40 belgidan uzun, lekin Postgres nom chegarasi — 63 bayt, sig'adi.
9. SQL qadami
Holat 23 → holat 34. Bazaga izohlar jadvali va namuna izohlar qo'shiladi. Fayllar:
sql/007-izohlar.sql— jadval, ikki tashqi kalit, CHECK, ikki indeks («Qoida: tashqi kalit "ko'p" tomonda» bo'limida to'liq matni);sql/008-namuna-izohlar.sql— 15 ta izoh, 11 ta vazifada; muallif — vazifa egasi; vaqtlar aniq ('2026-10-01 20:00+05').
Namuna fayli izohni vazifa matni orqali ulaydi — id qo'lda yozilmaydi (INSERT va RETURNING darsidagi naqsh):
INSERT INTO izohlar (vazifa_id, muallif_id, matn, yaratilgan_vaqt)
SELECT v.id, v.egasi_id, i.matn, i.vaqt::timestamptz
FROM (VALUES
('JavaScript kursining 26-qismini boshlash #o''qish', 'Docker darslaridan boshladim', '2026-10-01 20:00+05'),
-- … yana 14 qator …
) AS i(vazifa_matn, matn, vaqt)Bazani noldan qurib tekshiring (har fayl bitta tranzaksiyada, birinchi xatoda to'xtaydi) — kanon tekshir.sh 34 hamma jadvallarni namunasiz va namuna bilan qurib, exit 0 bergan. Keyin commit:
git add sql/007-izohlar.sql sql/008-namuna-izohlar.sql
git commit -m "db: izohlar (1:N) — vazifa CASCADE, muallif SET NULL (007, 008-namuna)"Holatdagi ongli qaror: hozir muallif doim vazifa egasi (bir kishilik ilova). muallif_id baribir alohida ustun — jamoaviy vazifalar keyingi qismlarda qo'shilganda sxema o'zgarmaydi.
10. Real ishda
- Har backend loyihasi. Buyurtma → buyurtma qatorlari (1:N), mahsulot ↔ kategoriya (N:M), foydalanuvchi → sozlamalar (1:1), izoh → javob (self). Bu to'rt shakldan boshqa narsa deyarli yo'q.
- ORM'lar. Prisma, TypeORM kabi vositalar
@relation,@ManyToManykabi yozuvlarni aynan shu jadvallarga aylantiradi. Ichida nima borligini bilsangiz, ORM xatolarini tez topasiz (27-qismda). - Ma'lumotni o'chirish siyosati. "Foydalanuvchi akkauntini o'chirdi — izohlari qoladimi?" — huquqiy savol ham (shaxsiy ma'lumotlar qonuni).
CASCADEyokiSET NULLqarori shu yerda. - Intervyuda: "N:M ni qanday saqlaysiz?", "1:1 qachon alohida jadval?", "FK qaysi tomonda?" — backend intervyusining birinchi daqiqalaridagi savollar.
Xulosa
- 1:N — tashqi kalit "ko'p" tomonda;
NOT NULL— majburiy ota, NULL — ixtiyoriy. ON DELETE— biznes qarori: CASCADE (bola ma'nosiz), SET NULL (bola qoladi), RESTRICT (o'chirish taqiq).- N:M — ko'prik jadval: ikki FK, tarkibiy PK, ikkinchi ustunga indeks.
- Ko'prik jadval juftlikning o'z ma'lumotini ham saqlay oladi (sana, son, baho).
- 1:1 — PK = FK (yoki FK + UNIQUE); faqat sababi bo'lsa alohida jadval.
- Self-relation — oddiy 1:N, faqat bitta jadval ichida.
Keyingi dars: Normalizatsiya — takrorlanadigan ma'lumot qanday anomaliyalar keltirib chiqarishini ko'ramiz va jadvallarni 1NF, 2NF, 3NF ga keltiramiz.
Manbalar
- PostgreSQL 18 hujjati: "Constraints — Foreign Keys" (
ON DELETE, tarkibiy kalitlar) va "CREATE TABLE" (TEMPORARY) — postgresql.org/docs/18, 2026-10-24 da ko'rildi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!