IlmHamroh
JavaScript Full-stack/26-qism. SQL va PostgreSQL40/64-dars20 daqiqa
Mundarija (28)

Amaliyot: real loyiha sxemasini loyihalash — talablardan review'gacha

Qisqacha: Real sxema to'rt qadamda quriladi. Talablardan entity va bog'lanishlar ajratiladi, nomlash va turlar kelishiladi. Asosiy so'rovlar uchun indekslar oldindan rejalanadi. Oxirida review — sxema katalog so'rovlari bilan tekshiriladi. Bu darsda vazifalar bazasini shu yo'ldan o'tkazamiz: review uchta muammo topadi, 011 fayli ularni tuzatadi va A bosqich yakunlanadi.

Bu darsda

  • Talablar matnidan entity, atribut va bog'lanishlarni ajratib, ER diagramma chizasiz.
  • Nomlash qoidalarini (snake_case, ko'plik, cheklov nomlari) bilasiz va ularni so'rov bilan tekshirasiz.
  • Indekslarni "keyin qo'shamiz" emas, asosiy so'rovlardan kelib chiqib rejalaysiz.
  • Sxema review'ini katalog so'rovlari bilan o'tkazasiz: tashqi kalit indeksi, nomlar, birlamchi kalit, izohlar.
  • vazifalar ning yakuniy sxemasini va undagi ongli qarorlarni tushuntira olasiz.

Oldin bilishingiz kerak: ER modellash va diagrammalar, Modellash naqshlari, Soft delete, timestamps va tarix jadvallari, Pul, miqdor va enum'larni modellash.

1. Nega bu kerak?

Oxirgi sakkiz darsda sxema bo'laklab o'sdi: izohlar, counter, teglar sinxroni, Savat, tarix. Har qadam o'zi to'g'ri edi. Lekin bo'laklar yig'ilganda yangi savollar tug'iladi: hamma tashqi kalitlarda indeks bormi? Nomlar bir xil uslubdami? Ko'rinish yangi ustunlarni ko'radimi? Asosiy so'rov tez ishlaydimi?

Real jamoada bu savollarni sxema review beradi — kod review'ining baza uchun shakli. U sxema production'ga chiqishidan oldin bo'ladi, chunki keyin har tuzatish — migratsiya, qulf va xavf.

O'xshatish: uy qurilishidagi qabul komissiyasi. Har usta o'z ishini yaxshi qilgan bo'lishi mumkin — elektrik, santexnik, suvoqchi. Lekin kimdir oxirida butun uyni bitta ro'yxat bo'yicha tekshirishi kerak: rozetka quvur ustidami, eshik ochilganda chiroq tugmasini to'smaydimi. Bu darsda biz shu komissiyamiz.

2. Talablardan entity'larga

2.1 Talablar

vazifalar ilovasining talablari — frontend (17–22-qismlar) va keyingi server (25-qism) dan:

  1. Foydalanuvchi email va ism bilan ro'yxatdan o'tadi; email registrga befarq noyob.
  2. Foydalanuvchi vazifa yozadi (1–100 belgi), belgilaydi, tahrirlaydi, o'chiradi.
  3. Bir foydalanuvchida bir xil vazifa ikki marta bo'lmaydi.
  4. Vazifa matnidagi #teg lar bo'yicha filtr va sanash.
  5. Vazifaga muddat, ustuvorlik (past, o'rta, yuqori) va kichik vazifalar.
  6. Vazifaga izohlar; muallif o'chsa izoh qoladi.
  7. O'chirilgan vazifa Savatga tushadi va tiklanadi.
  8. "Kim, qachon, nimani o'zgartirdi" tarixi.
  9. Ikki qurilmadan bir vaqtda tahrirlashda eski ma'lumot yangisini jim bosib ketmasin.
  10. Ro'yxat — yangilari birinchi, sahifalab.

2.2 Otlar → entity, fe'llar → bog'lanish

ER modellash darsidagi usul: otlarni belgilaymiz. Foydalanuvchi, vazifa, teg, izoh, tarix — beshta entity, oltinchisi N:M ko'prik (vazifa_teglari). "Muddat", "ustuvorlik", "Savat" — entity emas, vazifaning atributlari. "Kichik vazifa" — alohida entity emas, vazifaning o'ziga bog'lanishi.

Talab → sxema xaritasi:

Talab Sxemada
1 foydalanuvchilar, UNIQUE INDEX (lower(email))
2, 3 vazifalar.matn CHECK, partial unique (egasi_id, lower(matn))
4 teglar + vazifa_teglari, trigger bilan sinxron
5 muddat, ustuvorlik (enum), ota_id (tarkibiy FK)
6 izohlar, muallif_id … ON DELETE SET NULL
7 ochirilgan_vaqt, faol_vazifalar ko'rinishi
8 vazifa_tarixi + trigger, SET LOCAL
9 versiya (bu dars, 011)
10 Ro'yxat indeksi (bu dars, 011)

9- va 10-talablar hali sxemada yo'q — ularni review topadi.

2.3 Yakuniy ER diagramma

Kanon er-diagramma.mmd — holat 40 dagi sxemaning diagrammasi. Nimaga qarang: nuqtali chiziq — vazifa_tarixi, u tashqi kalitsiz (tarix jadvallari); muallif bog'lanishi — |o (SET NULL).

erDiagram
    foydalanuvchilar ||--o{ vazifalar : "egasi"
    foydalanuvchilar ||--o{ teglar : "egasi"
    foydalanuvchilar |o--o{ izohlar : "muallif (SET NULL)"
    vazifalar |o--o{ vazifalar : "ota_id (kichik vazifa)"
    vazifalar ||--o{ vazifa_teglari : "vazifa"
    teglar ||--o{ vazifa_teglari : "teg"
    vazifalar ||--o{ izohlar : "vazifa"
    vazifalar ||..o{ vazifa_tarixi : "tarix (FK yo'q)"

    foydalanuvchilar {
        bigint id PK
        text email UK "lower(email) noyob"
        text ism
        timestamptz yaratilgan_vaqt
    }
    vazifalar {
        bigint id PK
        bigint egasi_id FK
        text matn "1-100 belgi"
        boolean bajarildi
        timestamptz bajarilgan_vaqt
        ustuvorlik ustuvorlik "enum: past, orta, yuqori"
        timestamptz muddat
        bigint ota_id FK "(ota_id, egasi_id)"
        integer izohlar_soni "counter cache"
        timestamptz yaratilgan_vaqt
        timestamptz yangilangan_vaqt
        timestamptz ochirilgan_vaqt "soft delete"
        integer versiya "optimistik qulf"
    }
    teglar {
        bigint id PK
        bigint egasi_id FK
        text nom "kichik harf, # bilan"
    }
    vazifa_teglari {
        bigint vazifa_id PK, FK
        bigint teg_id PK, FK
    }
    izohlar {
        bigint id PK
        bigint vazifa_id FK
        bigint muallif_id FK
        text matn
        timestamptz yaratilgan_vaqt
    }
    vazifa_tarixi {
        bigint id PK
        bigint vazifa_id
        text amal "INSERT, UPDATE, DELETE"
        jsonb eski
        jsonb yangi
        bigint kim_id
        timestamptz vaqt
    }

Tekshirib ko'ring: "Savat" nega alohida jadval (savat) emas, vazifalar dagi ustun?

Javob

Savatdagi vazifa — o'sha vazifa, faqat holati boshqa. Alohida jadvalga ko'chirish izohlar, teglar va tarixni ham ko'chirishni yoki tashqi kalitlarni buzishni talab qilardi. Holat — atribut, entity emas.

Diagramma katta — telefon ekranida uni kattalashtirib ko'ring. Kanon render qilganda ota_id yorlig'i boshqa yorliq ustiga tushgan (o'qiladi, lekin chiroyli emas) — katta diagrammalarning odatiy muammosi.

3. Nomlash qoidalari

3.1 Kelishuv

Nomlash — jamoa kelishuvi. Muhimi — tanlangan qoidaga hamma joyda rioya qilish. vazifalar dagi qoidalar:

Nima Qoida Misol
Jadval snake_case, ko'plik vazifalar, vazifa_teglari
Ustun snake_case, birlik egasi_id, yaratilgan_vaqt
Tashqi kalit ustuni <ota birlikda>_id vazifa_id, muallif_id
Cheklov / indeks <jadval>_<ustun>_<tur> vazifalar_matn_check, izohlar_vazifa_id_fkey

Cheklov qo'shimchalari — Postgres o'zi beradigan andoza: _pkey, _key (UNIQUE), _fkey, _check, _idx. Nomni o'zingiz yozsangiz ham shu andozada yozing — xato xabarida (violates check constraint "vazifalar_matn_check") qaysi jadval va qaysi ustun ekani darhol ko'rinadi. Katta harf yo'q: Postgres qo'shtirnoqsiz nomni kichik harfga o'giradi va "Vazifalar" kabi nom har so'rovda qo'shtirnoq talab qiladi.

Nomlar o'zbekcha — kurs qarori (o'quvchi kalit so'zni o'z nomidan ajratadi). Real jamoada ko'pincha inglizcha (tasks, created_at) — qaysi til bo'lishidan qat'i nazar, bitta loyihada bitta til.

3.2 Review so'rovi: nomlar andozadami

Nomlash qoidasini so'rov bilan tekshirish mumkin: andozaga mos kelmaydigan cheklovlar. Kanon review so'rovi:

sql
SELECT conrelid::regclass AS jadval, conname FROM pg_constraint
WHERE connamespace = 'public'::regnamespace
  AND conname !~ '_(pkey|key|fkey|check)$'
ORDER BY 1;

Kanon (PostgreSQL 18.6) bu so'rovda 26 qator chiqargan — foydalanuvchilar_email_not_null, vazifalar_matn_not_null va boshqalar (to'liq ro'yxat kanon xom faylida). Kanonning qisqa hisobotida esa "andozadan chetga chiqqan cheklov 0" deb yozilgan — bu xom chiqishga zid, va biz buni ochiq aytamiz.

Sabab — PostgreSQL 18 yangiligi: NOT NULL endi pg_constraint katalogida alohida cheklov sifatida saqlanadi va <jadval>_<ustun>_not_null nomini oladi (postgresql.org/docs/18, "pg_constraint", contype = 'n'). PostgreSQL 17 va undan oldin ular u yerda yo'q edi. Ya'ni bu 26 qator — xato emas, Postgres'ning o'z andozasi; review so'rovi esa PG 17 uchun yozilgan. To'g'ri so'rov not_null ni ham andoza deb qabul qiladi. Biz o'z PostgreSQL 17.6 klasterimizda, holat 40 da ishga tushirdik:

sql
SELECT conrelid::regclass AS jadval, conname
FROM pg_constraint
WHERE connamespace = 'public'::regnamespace
  AND conname !~ '_(pkey|key|fkey|check|not_null)$'
ORDER BY 1;
text
 jadval | conname
--------+---------
(0 rows)

0 qator — hamma cheklov nomi andozada. PG 18 da ham shu so'rov 0 beradi (26 ta _not_null endi andozaga kiradi). Saboq: katalog so'rovlari Postgres versiyasiga bog'liq — yangi versiyaga o'tganda review skriptlarini qayta tekshiring.

Katta harfli nomlar ham tekshirildi (kanon chiqishi):

sql
SELECT relname, relkind FROM pg_class
WHERE relnamespace = 'public'::regnamespace AND relname ~ '[A-Z]';
text
 relname | relkind
---------+---------
(0 rows)

4. Indekslar — oldindan

4.1 Tashqi kalitlar indeksi

Foreign key darsidagi qoida: Postgres tashqi kalit ustuniga indeksni o'zi yaratmaydi. Indekssiz tashqi kalit ota qatorni o'chirishda (CASCADE uchun bola jadvalni qidirish) va JOIN'da sekin. Review so'rovi: har tashqi kalitning birinchi ustuni biror indeksning birinchi ustunimi? pg_index.indkey[0] — indeksning birinchi ustuni, conkey[1] — tashqi kalitning birinchi ustuni (Postgres massivlari bu ikki katalogda turlicha raqamlanadi). Kanon so'rovi:

sql
SELECT c.conrelid::regclass AS jadval, c.conname, a.attname AS ustun,
       EXISTS (SELECT 1 FROM pg_index i
               WHERE i.indrelid = c.conrelid
                 AND i.indkey[0] = c.conkey[1]) AS indeks_bor
FROM pg_constraint c
JOIN pg_attribute a
  ON a.attrelid = c.conrelid AND a.attnum = c.conkey[1]
WHERE c.contype = 'f' AND c.connamespace = 'public'::regnamespace
ORDER BY 1, 2;
text
     jadval     |            conname            |   ustun    | indeks_bor
----------------+-------------------------------+------------+------------
 vazifalar      | vazifalar_egasi_id_fkey       | egasi_id   | t
 vazifalar      | vazifalar_ota_id_fkey         | ota_id     | t
 teglar         | teglar_egasi_id_fkey          | egasi_id   | t
 vazifa_teglari | vazifa_teglari_teg_id_fkey    | teg_id     | t
 vazifa_teglari | vazifa_teglari_vazifa_id_fkey | vazifa_id  | t
 izohlar        | izohlar_muallif_id_fkey       | muallif_id | t
 izohlar        | izohlar_vazifa_id_fkey        | vazifa_id  | t
(7 rows)

Hammasi t. Ba'zilarining indeksi alohida emas — boshqa indeks qoplaydi: vazifa_teglari.vazifa_id — tarkibiy PK'ning birinchi ustuni, teglar.egasi_id — UNIQUE (egasi_id, nom) ning birinchi ustuni. Ortiqcha indeks yaratilmagan.

4.2 Asosiy so'rov uchun indeks

Indekslar "sekin bo'lsa qo'shamiz" emas, asosiy so'rovlardan rejalashtiriladi. Ilovaning eng ko'p bajariladigan so'rovi — 10-talab: "foydalanuvchining faol vazifalari, yangilari birinchi". Unga mos indeks — filtr (egasi_id), tartib (yaratilgan_vaqt DESC, id DESC) va faqat faollar (WHERE ochirilgan_vaqt IS NULL). Kanon 011 dan:

sql
CREATE INDEX vazifalar_royxat_idx
  ON vazifalar (egasi_id, yaratilgan_vaqt DESC, id DESC)
  WHERE ochirilgan_vaqt IS NULL;

id DESC — tartibni barqaror qiladi (bir xil vaqtli ikki vazifa) va keyinchalik sahifalashda kursor bo'ladi (Keyset pagination). Kanon reja chiqishi (PostgreSQL 18.6, holat 40):

sql
EXPLAIN (COSTS OFF)
SELECT id FROM faol_vazifalar
WHERE egasi_id = 1
ORDER BY yaratilgan_vaqt DESC, id DESC
LIMIT 5;
text
                          QUERY PLAN
---------------------------------------------------------------
 Limit
   ->  Index Only Scan using vazifalar_royxat_idx on vazifalar
         Index Cond: (egasi_id = 1)
(3 rows)

Index Only Scan — so'rov jadvalga umuman kirmay, faqat indeksdan javob oldi; saralash ham kerak bo'lmadi. Halol izoh: bizning PostgreSQL 17.6 klasterimizda xuddi shu so'rov Seq Scan + Sort rejasini berdi — 48 qatorli jadvalda butun jadvalni o'qish arzonroq va planner indeksni tanlamaydi. Indeks katta jadvalda ishlaydi; kanon buni 2 mln vazifali to'plamda o'lchagan — indekslar darslarida ko'ramiz. Rejalarni o'qishni EXPLAIN ANALYZE darsida o'rganamiz.

5. Review topilmalari va 011

5.1 Topilma: ko'rinish yangi ustunni ko'rmaydi

faol_vazifalar holat 38 da SELECT * bilan yaratilgan. SELECT * ko'rinish yaratilgan paytdagi ustunlar ro'yxatiga aylanadi — keyin qo'shilgan ustun ko'rinishga avtomatik kirmaydi. Kanon buni vaqtinchalik ko'rinishda isbotlagan:

sql
CREATE TEMP VIEW t40_eski AS
  SELECT * FROM vazifalar WHERE ochirilgan_vaqt IS NULL;
ALTER TABLE vazifalar ADD COLUMN t40_sinov int;
SELECT count(*) FILTER (WHERE attname = 't40_sinov') AS viewda_bormi
FROM pg_attribute WHERE attrelid = 't40_eski'::regclass;
ALTER TABLE vazifalar DROP COLUMN t40_sinov;
DROP VIEW t40_eski;
text
CREATE VIEW
ALTER TABLE
 viewda_bormi
--------------
            0
(1 row)

ALTER TABLE
DROP VIEW

Ustun jadvalda bor, ko'rinishda — yo'q. Shuning uchun 011 versiya ustunini qo'shgandan keyin ko'rinishni qayta yaratadi (CREATE OR REPLACE VIEW). Bu tuzoq jim: ilova ko'rinishdan o'qiydi va yangi maydon "yo'q" bo'lib qoladi.

5.2 Topilma: parallel tahrir (9-talab)

Aziz vazifani telefonda va kompyuterda ochdi. Telefonda matnni o'zgartirdi, keyin kompyuterda eski sahifadan muddatni saqladi — kompyuter eski matnni ham yubordi va telefondagi o'zgarish jim yo'qoldi. Bu "yo'qolgan yangilanish". Yechim — optimistik qulf (optimistic locking): har qatorda versiya raqami; tahrir faqat ilova ko'rgan versiya hali joriy bo'lsa o'tadi. Kanon 011 (izohlar qisqartirilgan):

sql
ALTER TABLE vazifalar ADD COLUMN versiya integer NOT NULL DEFAULT 1;

CREATE OR REPLACE FUNCTION yangilangan_vaqtni_qoy() RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
  NEW.yangilangan_vaqt := now();
  NEW.versiya := OLD.versiya + 1;   -- ilova o'zi oshirmaydi
  RETURN NEW;
END $$;

CREATE OR REPLACE VIEW faol_vazifalar AS
  SELECT * FROM vazifalar WHERE ochirilgan_vaqt IS NULL;

010 dagi funksiya o'zgartirilmadi — yangi fayl CREATE OR REPLACE bilan uni almashtirdi (migratsiya intizomi: qo'llangan fayl o'zgarmaydi). versiya ni trigger oshiradi — ilova faqat solishtiradi. Kanon review chiqishi: namuna 012 dagi tahrirlardan keyin bir nechta vazifa versiyasi 2. Izoh qo'shish versiyani oshirmaydi (counter "tahrir" emas):

sql
SELECT id, versiya FROM vazifalar WHERE id IN (8, 34, 38) ORDER BY id;
INSERT INTO izohlar (vazifa_id, muallif_id, matn)
VALUES (34, 1, 'Izoh versiyani oshirmaydi');
SELECT id, versiya, izohlar_soni FROM vazifalar WHERE id = 34;
text
 id | versiya
----+---------
  8 |       2
 34 |       2
 38 |       2
(3 rows)

INSERT 0 1
 id | versiya | izohlar_soni
----+---------+--------------
 34 |       2 |            1
(1 row)

Endi parallel tahrir: kompyuter eski sahifadan (versiya 1) yozmoqchi, telefon — joriy (versiya 2) bilan:

sql
UPDATE vazifalar SET matn = 'Eski versiyadan tahrir'
WHERE id = 34 AND versiya = 1
RETURNING id;
UPDATE vazifalar SET matn = 'SQL JOIN va subquery mashqlari #o''qish'
WHERE id = 34 AND versiya = 2
RETURNING id, versiya;
text
 id
----
(0 rows)

UPDATE 0
 id | versiya
----+---------
 34 |       3
(1 row)

UPDATE 1

Eski versiya bilan — UPDATE 0: hech narsa yozilmadi, xato ham yo'q. Ilova buni "vazifa boshqa joyda o'zgartirilgan, sahifani yangilang" deb aytadi: versiya If-Match sarlavhasida kelsa — 412, so'rov tanasida kelsa — 409. Joriy versiya bilan — o'tdi, versiya 3. Bu naqshni Node kodida Optimistic concurrency darsida to'liq quramiz.

5.3 Hujjat — bazaning o'zida

011 ning oxiri — COMMENT ON: har jadval va muhim ustunga izoh. U \d+ da, GUI vositalarda va katalogda ko'rinadi — hujjat sxema bilan birga yashaydi va eskirmaydi. Kanon 011 dan bir nechtasi:

sql
COMMENT ON TABLE izohlar IS '1:N — vazifa izohlari';
COMMENT ON COLUMN vazifalar.ochirilgan_vaqt
  IS 'NULL — faol; vaqt — Savatda (yumshoq o''chirilgan)';
COMMENT ON COLUMN vazifalar.versiya
  IS 'Optimistik qulf: har tahrirda trigger +1 qiladi';

Kanonda izohlar_soni ga ham izoh bor: "qo'lda yozilmaydi". Bunday izoh — keyingi dasturchiga eng qimmatli ogohlantirish.

Tekshirib ko'ring: Nega versiya ni ilova SET versiya = versiya + 1 bilan emas, trigger oshiradi?

Javob

Ilova kodida bir joyda unutilsa (admin panel, import skripti), versiya o'zgarmaydi va optimistik tekshiruv yo'qolgan yangilanishni ushlamaydi. Trigger har tahrirda kafolat beradi — yangilangan_vaqt bilan bir xil sabab (Soft delete va tarix).

6. Asosiy so'rov: API ro'yxati

Hammasi yig'ilganda ilovaning ro'yxat so'rovi shunday bo'ladi — 25-qism API'si qaytaradigan shaklda, teglar massiv bilan. Kanon so'rovi (qatorlarga bo'lingan):

sql
SELECT v.id, v.matn, v.bajarildi, v.ustuvorlik, v.muddat,
       v.izohlar_soni, v.versiya,
       coalesce(array_agg(t.nom ORDER BY t.nom)
                  FILTER (WHERE t.id IS NOT NULL), '{}') AS teglar
FROM faol_vazifalar v
LEFT JOIN vazifa_teglari vt ON vt.vazifa_id = v.id
LEFT JOIN teglar t ON t.id = vt.teg_id
WHERE v.egasi_id = 1
GROUP BY v.id, v.matn, v.bajarildi, v.ustuvorlik, v.muddat,
         v.izohlar_soni, v.versiya, v.yaratilgan_vaqt
ORDER BY v.yaratilgan_vaqt DESC, v.id DESC
LIMIT 5;
text
 id |                      matn                      | bajarildi | ustuvorlik |         muddat         | izohlar_soni | versiya |    teglar
----+------------------------------------------------+-----------+------------+------------------------+--------------+---------+---------------
 48 | Bozordan kartoshka va piyoz olish #bozor       | f         | orta       |                        |            0 |       1 | {#bozor}
 38 | Velosiped zanjirini moylash #ta'mir            | f         | past       |                        |            0 |       2 | {#ta'mir}
 34 | SQL JOIN va subquery mashqlari #o'qish         | t         | orta       | 2026-10-25 16:00:00+00 |            1 |       3 | {#o'qish}
 28 | Elektr to'lovini Click orqali qilish #to'lov   | t         | yuqori     | 2026-10-10 18:59:00+00 |            0 |       1 | {#to'lov}
 23 | Oshxonadagi jo'mrakni almashtirish #ta'mir #uy | f         | yuqori     | 2026-10-20 13:00:00+00 |            2 |       1 | {#ta'mir,#uy}
(5 rows)

Har qatorda frontend kutadigan hamma narsa: matn, holat, ustuvorlik, muddat, izohlar soni, versiya (keyingi tahrir uchun) va teglar. 8-vazifa Savatda (012 namunasi uni o'chirgan) — ro'yxatda yo'q; uning o'rniga yangi 48-vazifa bir xil matn bilan. FILTER (WHERE t.id IS NOT NULL) — tegsiz vazifada {NULL} emas, {} chiqishi uchun (Agregatlar).

7. Review ro'yxati

Har yangi sxema yoki migratsiya uchun shu savollar:

# Savol Qanday tekshiriladi
1 Har jadvalda birlamchi kalit bormi? pg_constraint, contype = 'p'
2 Har tashqi kalit ustunida indeks bormi? «Tashqi kalitlar indeksi» so'rovi
3 Nomlar andozadami? «Nomlar andozadami» so'rovi
4 NOT NULL va CHECK biznes qoidalariga mosmi? Talablar jadvali bilan solishtirish
5 Har ON DELETE o'ylanganmi? Bog'lanishlar darsidagi FK xaritasi
6 Asosiy so'rovlar uchun indeks bormi? EXPLAIN (katta to'plamda)
7 Ko'rinishlar yangi ustunlarni ko'radimi? pg_attribute
8 Pul, vaqt, ID turlari to'g'rimi? numeric/bigint, timestamptz, bigint/uuid
9 Jadval va murakkab ustunlarda izoh bormi? obj_description

Review avtomatlashtirilishi mumkin: so'rovlar alohida faylda turadi va har migratsiyadan keyin ishga tushadi — "0 qator" kutiladi (1-va 2-mashqlar).

8. Ongli qarorlar — review'da qolganlari

Review har narsani "tuzatish" emas. Ba'zi joylar ataylab shunday qoldirildi va bu yozib qo'yilishi kerak:

  • vazifa_teglari teg va vazifa egasi bir xilligini bazada kafolatlamaydi. Trigger to'g'ri yozadi, lekin qo'lda INSERT boshqa foydalanuvchining tegini ulashi mumkin. To'liq kafolat uchun ko'prikka egasi_id va tarkibiy tashqi kalitlar kerak — kurs qarori: hozircha trigger kafolati yetarli.
  • Tarkibiy tashqi kalit (ota_id, egasi_id) narxi — qo'shimcha UNIQUE (id, egasi_id) indeksi. Kanon katta to'plamda (2 mln vazifa) bu indeks taxminan 111 MB ekanini o'lchagan; kurs qarori: kafolat narxiga arziydi.
  • matn = btrim(matn) — Postgres btrim faqat probelni kesadi, JS trim() esa hamma bo'shliq belgilarini (tab, NBSP). Frontend bilan farq — ilova tekshiruvi qo'shimcha.
  • Parol va auth maydonlari yo'q — 28-qismda qo'shiladi.
  • tartib ustuni yo'q (qo'lda saralash) — 25-qism API'si uni talab qiladi; node-postgres asoslari darsida yangi fayl (013) bilan qo'shiladi.
  • ID — bigint: Node drayveri uni satr qaytaradi — node-postgres asoslari da hal qilinadi.

9. Ko'p uchraydigan xatolar

1. Review'siz production. Sxema xatosi kod xatosidan qimmat: tuzatish uchun ma'lumotni ko'chirish kerak. Tuzatish: har sxema o'zgarishi — review ro'yxati bilan.

2. Indeksni "keyin". Jadval kichikligida hamma narsa tez — muammo 100 000-foydalanuvchida chiqadi. Tuzatish: asosiy so'rovlar ro'yxati va ularning indekslari — loyihalash bosqichida.

3. SELECT * ko'rinish. Yangi ustun ko'rinishga kirmaydi. Tuzatish: ustun qo'shilganda ko'rinishni qayta yarating yoki ustunlarni aniq sanab yozing.

4. Qo'llangan migratsiya faylini tahrirlash. Bir bazada eski variant, boshqasida yangi — ular endi farq qiladi. Tuzatish: yangi fayl (011) va CREATE OR REPLACE.

5. Versiyaga bog'liq review so'rovlari. PG 18 dagi _not_null misoli: eski so'rov yangi versiyada "26 muammo" topdi. Tuzatish: versiya o'zgarganda review skriptini qayta tekshirish.

6. Ongli qarorni yozmaslik. Bir yildan keyin kimdir "bu xato" deb "tuzatadi". Tuzatish: COMMENT ON va qarorlar hujjati.

10. Mashqlar

Holat 40 da.

1-mashq (oson)

Review: har jadval uchun birlamchi kaliti bormi (pk_bor) va izohi bormi (izoh_bor) — bitta so'rovda. Ishora: jadvallar — pg_class (relkind = 'r'), izoh — obj_description(oid), birlamchi kalit — pg_constraint (contype = 'p'), EXISTS — Subquery.

Yechim
sql
SELECT c.relname AS jadval,
       EXISTS (SELECT 1 FROM pg_constraint p
               WHERE p.conrelid = c.oid AND p.contype = 'p')
         AS pk_bor,
       obj_description(c.oid) IS NOT NULL AS izoh_bor
FROM pg_class c
WHERE c.relnamespace = 'public'::regnamespace AND c.relkind = 'r'
ORDER BY 1;
text
      jadval      | pk_bor | izoh_bor
------------------+--------+----------
 foydalanuvchilar | t      | t
 izohlar          | t      | t
 teglar           | t      | t
 vazifa_tarixi    | t      | t
 vazifa_teglari   | t      | t
 vazifalar        | t      | t
(6 rows)

Hamma jadvalda ikkalasi ham bor. Avtomatik review'da bu so'rov WHERE NOT pk_bor OR NOT izoh_bor bilan yoziladi va "0 qator" kutiladi.

2-mashq (o'rta)

Review so'rovi xatoni topishini sinab ko'ring: tranzaksiyada indekssiz tashqi kalitli eslatma_sinov (id, vazifa_id → vazifalar) jadvalini yarating va faqat indeksi yo'q tashqi kalitlarni chiqaring (jadval, ustun). Ishora: «Tashqi kalitlar indeksi» so'rovi, EXISTS o'rniga NOT EXISTS — WHERE da.

Yechim
sql
BEGIN;
CREATE TABLE eslatma_sinov (
  id        bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  vazifa_id bigint NOT NULL REFERENCES vazifalar ON DELETE CASCADE
);
SELECT c.conrelid::regclass AS jadval, a.attname AS ustun
FROM pg_constraint c
JOIN pg_attribute a
  ON a.attrelid = c.conrelid AND a.attnum = c.conkey[1]
WHERE c.contype = 'f'
  AND c.connamespace = 'public'::regnamespace
  AND NOT EXISTS (SELECT 1 FROM pg_index i
                  WHERE i.indrelid = c.conrelid
                    AND i.indkey[0] = c.conkey[1]);
ROLLBACK;
text
BEGIN
CREATE TABLE
    jadval     |   ustun
---------------+-----------
 eslatma_sinov | vazifa_id
(1 row)

ROLLBACK

Yangi jadval ushlandi — eski yettita tashqi kalit ro'yxatda yo'q. Review so'rovi shu shaklda CI'da ishlasa, indekssiz tashqi kalit hech qachon production'ga chiqmaydi.

3-mashq (qiyin)

Yangi talab keldi. Vazifaga bir nechta eslatma vaqti qo'yish mumkin. Bir vazifaga bir vaqtda ikki eslatma bo'lmasin. Server har daqiqada yuborilmagan, vaqti kelgan eslatmalarni qidiradi. eslatmalar jadvalini andoza nomlari bilan loyihalang (vazifa o'chsa — eslatmalar ham), "kutilayotganlar" so'rovi uchun partial indeks qo'shing. Uchta eslatma yozing (1-vazifaga 31-oktyabr 09:00 va 20:00, 5-vazifaga 25-oktyabr 08:00, Toshkent vaqti), 1-noyabrgacha bo'lgan eng yaqin ikkitasini matni bilan chiqaring, keyin takror eslatmani qo'shib ko'ring. Hammasi tranzaksiyada. Ishora: noyoblik — UNIQUE (vazifa_id, vaqt); partial indeks — WHERE NOT yuborildi (Soft delete darsidagi partial unique kabi).

Yechim
sql
BEGIN;
CREATE TABLE eslatmalar (
  id         bigint      GENERATED ALWAYS AS IDENTITY,
  vazifa_id  bigint      NOT NULL,
  vaqt       timestamptz NOT NULL,
  yuborildi  boolean     NOT NULL DEFAULT false,
  CONSTRAINT eslatmalar_pkey PRIMARY KEY (id),
  CONSTRAINT eslatmalar_vazifa_id_fkey FOREIGN KEY (vazifa_id)
    REFERENCES vazifalar (id) ON DELETE CASCADE,
  CONSTRAINT eslatmalar_vazifa_id_vaqt_key UNIQUE (vazifa_id, vaqt)
);
CREATE INDEX eslatmalar_kutilayotgan_idx ON eslatmalar (vaqt)
  WHERE NOT yuborildi;
INSERT INTO eslatmalar (vazifa_id, vaqt) VALUES
  (1, '2026-10-31 09:00+05'), (1, '2026-10-31 20:00+05'),
  (5, '2026-10-25 08:00+05');
SET TIME ZONE 'Asia/Tashkent';
SELECT e.vaqt, v.matn
FROM eslatmalar e JOIN vazifalar v ON v.id = e.vazifa_id
WHERE NOT e.yuborildi AND e.vaqt < '2026-11-01 00:00+05'
ORDER BY e.vaqt
LIMIT 2;
INSERT INTO eslatmalar (vazifa_id, vaqt)
VALUES (1, '2026-10-31 09:00+05');
text
BEGIN
CREATE TABLE
CREATE INDEX
INSERT 0 3
SET
          vaqt          |                       matn
------------------------+--------------------------------------------------
 2026-10-25 08:00:00+05 | Ingliz tili: 20 ta yangi so'z #o'qish #ingliz
 2026-10-31 09:00:00+05 | JavaScript kursining 26-qismini boshlash #o'qish
(2 rows)

ERROR:  duplicate key value violates unique constraint "eslatmalar_vazifa_id_vaqt_key"
DETAIL:  Key (vazifa_id, vaqt)=(1, 2026-10-31 09:00:00+05) already exists.

Review ro'yxati bo'yicha: PK bor, tashqi kalit ustuni UNIQUE (vazifa_id, vaqt) indeksining birinchi ustuni — alohida indeks kerak emas, nomlar andozada, vaqt — timestamptz. Partial indeks faqat yuborilmaganlarni saqlaydi — yuborilganlar ko'paysa ham u kichik qoladi. Xatodan keyin ROLLBACK; bilan yoping.

11. SQL qadami

Holat 38 → holat 40 — A bosqichning yakuniy sxemasi. Ikki fayl:

  • sql/011-yakuniy.sql — versiya + trigger funksiyasini almashtirish, faol_vazifalar ni qayta yaratish, vazifalar_royxat_idx, COMMENT lar;
  • sql/012-namuna-yakuniy.sql — trigger'lar ishlaganini ko'rsatadigan 4 ta namuna amal: SET LOCAL ilova.foydalanuvchi_id = '1', bitta vazifa bajarildi, biriga teg qo'shildi, biri Savatga tushdi va xuddi shu matn bilan yangisi yozildi. Natijada tarixda 4 yozuv, tahrirlangan vazifalar versiyasi 2. Bu faylda vaqt ustunlari now() dan — boshqa namuna fayllardan farqli, ular har qurishda boshqacha.

Kanon tekshiruvi: hamma 9 holat (12, 14, 15, 21, 23, 34, 36, 38, 40) namunasiz va namuna bilan noldan quriladi — exit 0. Yakuniy sxema: 6 jadval, 1 ko'rinish, 1 enum, 5 funksiya, 4 trigger; namuna ma'lumot — 8 foydalanuvchi, 48 vazifa, 32 teg, 48 bog'lanish, 15 izoh, 4 tarix yozuvi. Biz ham holat 40 ni o'z PostgreSQL 17.6 klasterimizda noldan qurdik — xatosiz.

Commit:

bash
git add sql/011-yakuniy.sql sql/012-namuna-yakuniy.sql
M="db: sxema review — versiya (optimistik qulf), ro'yxat indeksi,"
M+=" view yangilandi, COMMENT'lar (011, 012-namuna)"
git commit -m "$M"

SQL qadami shu yerda tugaydi. Keyingi darslar — Node qadami: shu bazaga Node.js'dan ulanamiz, parametrli so'rovlar, pool, tranzaksiyalar, qulflar, indekslar va katta to'plamdagi o'lchovlar. 25-qismdagi vazifalar-api ning xotiradagi ombori (repository) shu sxemaga ko'chadi: node-postgres asoslari darsidan boshlab yangi PgOmbori yoziladi.

12. Real ishda

  • Design review / RFC. Yirik jamoalarda yangi jadval "dizayn hujjati" bilan keladi: talablar, ER diagramma, asosiy so'rovlar va indekslar, migratsiya rejasi. Bu dars aynan shu hujjatning qisqa shakli.
  • CI'da sxema tekshiruvi. Review so'rovlari (indekssiz FK, PK'siz jadval, nomlar) har migratsiyadan keyin avtomatik ishlaydi — xato production'ga yetmaydi.
  • Onboarding. Yangi dasturchiga ER diagramma va COMMENT lar sxemani bir soatda tushuntiradi.
  • Intervyuda: "Ta'lim platformasi (yoki onlayn do'kon) uchun sxema loyihalang" — backend intervyusining asosiy vazifalaridan. Baholanadi: entity'lar, bog'lanishlar, kalitlar, indekslar va siz aytgan ongli qarorlar — nima uchun aynan shunday.

Xulosa

  • Talablar → otlar (entity) va fe'llar (bog'lanish) → ER diagramma → talab–sxema jadvali.
  • Nomlash — bitta kelishuv: snake_case, ko'plik, <jadval>_<ustun>_<tur> cheklov nomlari.
  • Indekslar — asosiy so'rovlardan: har tashqi kalitga va eng ko'p so'rovga (filtr + tartib + partial).
  • Review — katalog so'rovlari bilan; PG 18 da NOT NULL ham pg_constraint da (_not_null) — skriptni yangilang.
  • SELECT * ko'rinish yangi ustunni ko'rmaydi; optimistik qulf — versiya + WHERE versiya = $2.
  • Ongli qarorlarni yozing: COMMENT ON va qarorlar hujjati.

Keyingi dars: node-postgres (pg) asoslari — Node.js'dan vazifalar bazasiga ulanamiz: so'rov, natija obyekti va Postgres turlari JavaScript'da qanday ko'rinishi.

Manbalar

  • PostgreSQL 18 hujjati: "pg_constraint" (contype = 'n', NOT NULL cheklovlari), "pg_index", "CREATE VIEW" (SELECT * va ustunlar ro'yxati), "COMMENT", "Release Notes 18" — postgresql.org/docs/18, 2026-10-24 da ko'rildi.
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Amaliyot: real loyiha sxemasini loyihalash — talablardan review'gacha — IlmHamroh