Mundarija (27)
- Bu darsda
- 1. Nega bu kerak?
- 2. WITH asoslari
- 2.1 Ikki bosqichli so'rov
- 2.2 Bitta CTE — bir necha marta
- 2.3 Matryoshkadan retseptga
- 3. Ma'lumot o'zgartiradigan CTE
- 3.1 O'chir va arxivla — bitta buyruqda
- 3.2 Zanjir: yangi id ni keyingi INSERT'ga uzatish
- 3.3 Bitta snapshot qoidasi
- 4. MATERIALIZED va NOT MATERIALIZED
- 4.1 CTE optimizatsiya to'sig'i edimi?
- 4.2 Qachon qaysi biri
- 4.3 CTE, ichki so'rov yoki vaqtinchalik jadval?
- 5. Ko'p uchraydigan xatolar
- 5.1 CTE keyingi buyruqda yo'q
- 5.2 Ustunlar soni mos emas
- 5.3 CTE'lar orasida vergul yoki WITH takrori
- 5.4 O'zgartiruvchi CTE'dan keyin eski qiymat
- 6. Mashqlar
- 1-mashq (oson): ichki so'rovni CTE'ga
- 2-mashq (o'rta): ikki bosqich
- 3-mashq (qiyin): yangila va hisobot ber
- 4-mashq: SQL qadami — ro'yxat va jami soni
- 7. Real ishda
- Xulosa
- Manbalar
SQL CTE (WITH): murakkab so'rovni nomlangan bosqichlarga bo'lish
Qisqacha: CTE (Common Table Expression — umumiy jadval ifodasi) —
WITH nom AS (SELECT ...)bilan so'rov boshida e'lon qilinadigan nomlangan vaqtinchalik natija. U ichma-ich ichki so'rovlarni yuqoridan pastga o'qiladigan bosqichlarga aylantiradi. CTE ichidaINSERT/UPDATE/DELETE ... RETURNINGham bo'ladi — masalan qatorlarni bitta buyruqda o'chirib arxivga ko'chirish. PostgreSQL 12 dan beri bir marta ishlatilgan CTE asosiy so'rovga "eritiladi";MATERIALIZEDbilan alohida hisoblashga majburlash mumkin.
Bu darsda
WITHbilan bir nechta nomlangan bosqichli so'rov yozasiz.- Bitta CTE natijasini so'rovda bir necha marta ishlatasiz.
DELETE ... RETURNINGli CTE bilan qatorlarni arxivga ko'chirasiz va "bitta snapshot" qoidasini tushuntirasiz.MATERIALIZEDvaNOT MATERIALIZEDfarqiniEXPLAINda ko'rasiz.- API uchun "ro'yxat + jami soni" so'rovini yozasiz.
Oldin bilishingiz kerak: Subquery va LATERAL, UPDATE va DELETE xavfsiz, JOIN'lar.
1. Nega bu kerak?
Subquery va LATERAL darsida hosila jadval bilan o'rtachalarni hisobladik. So'rov ishladi, lekin o'qishga qiyin edi: qavs ichida qavs, mantiq ichkaridan tashqariga qarab o'qiladi. Uch-to'rt bosqichli hisobotda bu "matryoshka" ga aylanadi.
Odam esa masalani bosqichma-bosqich o'ylaydi:
- Har foydalanuvchining statistikasini hisobla.
- Ulardan faollarini (5+ vazifa) ajrat.
- Ismlarini qo'shib, foizni chiqar.
CTE aynan shunday yozishga imkon beradi: har bosqichga nom berasiz va keyingi bosqich uni oddiy jadval kabi ishlatadi. O'xshatish: osh retsepti. "Go'shtni qovur → sabzini sol → guruchni qo'sh" — har qadam oldingisining natijasidan foydalanadi. Hammasini bitta uzun gapga tiqsangiz ham osh pishadi, lekin retseptni hech kim tushunmaydi.
2. WITH asoslari
2.1 Ikki bosqichli so'rov
Kanon tajribasi (PostgreSQL 18.6, vazifalar holat 23):
WITH statistika AS (
SELECT egasi_id, count(*) AS jami,
count(*) FILTER (WHERE bajarildi) AS bajarilgan
FROM vazifalar GROUP BY egasi_id
), faollar AS (
SELECT * FROM statistika WHERE jami >= 5
)
SELECT f.ism, s.jami, s.bajarilgan,
round(100.0 * s.bajarilgan / s.jami) AS foiz
FROM faollar s JOIN foydalanuvchilar f ON f.id = s.egasi_id
ORDER BY foiz DESC, f.ism; ism | jami | bajarilgan | foiz
------------------+------+------------+------
Dilnoza Rahimova | 6 | 3 | 50
Javohir Olimov | 6 | 3 | 50
Sardor Tursunov | 6 | 3 | 50
Madina Yusupova | 9 | 4 | 44
Aziz Karimov | 12 | 5 | 42
(5 rows)Tuzilishini qismlarga ajratamiz:
| Qism | Ma'nosi |
|---|---|
WITH statistika AS (...) |
1-bosqich: nomi statistika |
, faollar AS (...) |
2-bosqich: vergul bilan, oldingisini ishlatadi |
oxirgi SELECT |
asosiy so'rov: bosqichlarni jadval kabi ishlatadi |
flowchart LR
V["vazifalar"] --> S["statistika<br/>har odam: jami, bajarilgan"]
S --> F["faollar<br/>jami >= 5"]
F --> A["asosiy SELECT<br/>+ ism, foiz"]
U["foydalanuvchilar"] --> ADiagramma — so'rovning o'zi: ma'lumot chapdan o'ngga oqadi, har quti — bitta nomlangan bosqich. Ikki so'z eslab qoling: CTE faqat shu bitta so'rov ichida yashaydi. Keyingi buyruqda statistika endi yo'q.
100.0 * s.bajarilgan / s.jami — 100.0 (kasr) birinchi turgani uchun bo'lish butun son bo'linmasi bo'lmaydi (Ifodalar va funksiyalar darsidagi 7/2 = 3 tuzog'i).
2.2 Bitta CTE — bir necha marta
Ichki so'rovni ikki joyda ishlatish uchun uni ikki marta yozish kerak edi. CTE'ga esa nom bilan istalgancha murojaat qilish mumkin. Har odamning vazifalari soni va o'rtachadan farqi (biz ishga tushirdik, PostgreSQL 17.6):
WITH soni AS (
SELECT egasi_id, count(*) AS jami FROM vazifalar GROUP BY egasi_id
)
SELECT f.ism, s.jami,
round(s.jami - (SELECT avg(jami) FROM soni), 1) AS farq
FROM soni s JOIN foydalanuvchilar f ON f.id = s.egasi_id
ORDER BY s.jami DESC, f.ism; ism | jami | farq
------------------+------+------
Aziz Karimov | 12 | 5.3
Madina Yusupova | 9 | 2.3
Dilnoza Rahimova | 6 | -0.7
Javohir Olimov | 6 | -0.7
Sardor Tursunov | 6 | -0.7
Bekzod Nazarov | 4 | -2.7
Gulnora Saidova | 4 | -2.7
(7 rows)soni ikki marta ishlatildi: JOIN'da va o'rtacha uchun ichki so'rovda. O'rtacha 6.71 (Subquery va LATERAL darsidagi bilan bir xil), Aziz undan 5.3 ga ko'p.
2.3 Matryoshkadan retseptga
Subquery va LATERAL darsidagi masalani eslang: "o'zining o'rtacha bajarish vaqtidan sekin bajarilgan vazifalar". U yerda hosila jadval bilan yozdik. Endi savolni kengaytiramiz: har odamda nechta shunday "sekin" vazifa bor? CTE bilan har bosqich alohida va nomli (biz ishga tushirdik, PostgreSQL 17.6):
WITH ortacha AS (
SELECT egasi_id,
avg(bajarilgan_vaqt - yaratilgan_vaqt) AS qiymat
FROM vazifalar WHERE bajarildi
GROUP BY egasi_id
), sekinlar AS (
SELECT v.egasi_id, v.matn
FROM vazifalar v JOIN ortacha o ON o.egasi_id = v.egasi_id
WHERE v.bajarildi
AND v.bajarilgan_vaqt - v.yaratilgan_vaqt > o.qiymat
)
SELECT f.ism, count(*) AS sekin
FROM sekinlar s JOIN foydalanuvchilar f ON f.id = s.egasi_id
GROUP BY f.id ORDER BY f.id; ism | sekin
------------------+-------
Aziz Karimov | 1
Dilnoza Rahimova | 2
Sardor Tursunov | 1
Madina Yusupova | 2
Javohir Olimov | 1
Gulnora Saidova | 1
Bekzod Nazarov | 1
(7 rows)Uch bosqichni yuqoridan pastga, oddiy gap kabi o'qish mumkin: "o'rtachalar → sekinlar → odam bo'yicha sanash". Har bosqichni alohida tekshirish ham oson: asosiy so'rovni vaqtincha SELECT * FROM ortacha ga almashtirasiz va oraliq natijani ko'rasiz. Ichma-ich ichki so'rovlarda buni qilish ancha qiyin.
Jami 9 ta — o'tgan darsdagi ro'yxat bilan bir xil (1 + 2 + 1 + 2 + 1 + 1 + 1). CTE natijani o'zgartirmaydi, faqat o'qishni osonlashtiradi.
Tekshirib ko'ring:
faollarCTE'sinistatistikadan oldin yozsak (WITH faollar AS (SELECT * FROM statistika ...), statistika AS (...)) ishlaydimi?
Javob
Yo'q. Oddiy (rekursiv bo'lmagan) WITH da har CTE faqat o'zidan oldin e'lon qilinganlarni ko'radi. Postgres relation "statistika" does not exist deydi. Tartib — retseptdagidek: avval qovuriladi, keyin solinadi.
3. Ma'lumot o'zgartiradigan CTE
3.1 O'chir va arxivla — bitta buyruqda
CTE ichida faqat SELECT emas, INSERT, UPDATE, DELETE ham bo'lishi mumkin — RETURNING bilan (INSERT va RETURNING). Bu data-modifying CTE (ma'lumot o'zgartiruvchi CTE).
Vazifa: 5-oktyabrdan oldin bajarilgan vazifalarni asosiy jadvaldan olib, arxivga ko'chirish. Sinov uchun t26_arxiv — vazifalar bilan bir xil tuzilmali vaqtinchalik jadval (LIKE vazifalar). Hammasi BEGIN ... ROLLBACK ichida, bazani buzmaslik uchun. Chiqishda ikki narsaga qarang: RETURNING qaytargan qatorlar va arxivda soni:
CREATE TEMP TABLE t26_arxiv (LIKE vazifalar);
BEGIN;
WITH kochdi AS (
DELETE FROM vazifalar
WHERE bajarildi AND bajarilgan_vaqt < '2026-10-05'
RETURNING *
)
INSERT INTO t26_arxiv SELECT * FROM kochdi RETURNING id, matn;
SELECT count(*) AS arxivda FROM t26_arxiv;
ROLLBACK;CREATE TABLE
BEGIN
id | matn
----+-------------------------------------------------
2 | Postgres'ni Docker'da ko'tarish #o'qish #docker
3 | Mijozga taklifnoma yuborish #ish
4 | Somsa uchun xamir tayyorlash #oshxona
5 | Ingliz tili: 20 ta yangi so'z #o'qish #ingliz
8 | Bozordan kartoshka va piyoz olish #bozor
9 | Onamga dori olib borish #oila
13 | Kutubxonaga kitob qaytarish #o'qish
(7 rows)
INSERT 0 7
arxivda
---------
7
(1 row)
ROLLBACKDELETE ... RETURNING * o'chirilgan qatorlarni qaytaradi, INSERT ... SELECT * FROM kochdi ularni arxivga yozadi. Ikki amal — bitta buyruq, demak bitta atomik ish: yo ikkalasi ham bo'ladi, yo hech biri. Alohida DELETE va INSERT qilsangiz, ular orasida xato chiqib, qatorlar yo'qolishi mumkin edi (tranzaksiyasiz). Bu kanon tajribasi.
Ichkaridagi RETURNING * ning * i to'g'ri ishlaydi, chunki arxiv jadvali LIKE vazifalar — ustunlar va tartib bir xil.
3.2 Zanjir: yangi id ni keyingi INSERT'ga uzatish
Ro'yxatdan o'tishda ilova ko'pincha ikki narsani birga yaratadi: foydalanuvchi va unga "birinchi vazifa" (onboarding). Ikkinchi INSERT ga birinchisining yangi id si kerak. CTE bilan bu bitta buyruq (biz ishga tushirdik, PostgreSQL 17.6; oxirida ROLLBACK):
BEGIN;
WITH yangi AS (
INSERT INTO foydalanuvchilar (email, ism)
VALUES ('lola@example.com', 'Lola Qodirova')
RETURNING id
)
INSERT INTO vazifalar (egasi_id, matn)
SELECT id, 'IlmHamroh''da birinchi darsni o''qish' FROM yangi
RETURNING id, egasi_id, matn;
ROLLBACK;BEGIN
id | egasi_id | matn
----+----------+-------------------------------------
48 | 9 | IlmHamroh'da birinchi darsni o'qish
(1 row)
INSERT 0 1
ROLLBACKyangi CTE foydalanuvchini qo'shib, uning id sini (9) qaytardi, asosiy INSERT uni egasi_id ga qo'ydi. Server kodida buni ikki so'rov qilib yozish ham mumkin — RETURNING id ni olib, keyingi so'rovga parametr qilib. Lekin unda ikki so'rov orasida tranzaksiya kerak bo'ladi (Tranzaksiya asoslari). Bitta buyruq o'z-o'zidan atomik.
3.3 Bitta snapshot qoidasi
Muhim nozik joy. Bitta WITH ning hamma qismlari — CTE'lar ham, asosiy so'rov ham — ma'lumotning bir xil snapshot'ini — buyruq boshlangan lahzadagi holatini — ko'radi. CTE'dagi o'zgarish asosiy so'rovga ko'rinmaydi:
BEGIN;
WITH ozgardi AS (
UPDATE vazifalar SET bajarildi = true,
bajarilgan_vaqt = '2026-10-20 12:00+05'
WHERE id = 1 RETURNING id
)
SELECT (SELECT bajarildi FROM vazifalar WHERE id = 1)
AS asosiy_soroq_korgani,
(SELECT count(*) FROM ozgardi) AS yangilandi;
ROLLBACK;BEGIN
asosiy_soroq_korgani | yangilandi
----------------------+------------
f | 1
(1 row)
ROLLBACKCTE 1-vazifani yangiladi (yangilandi = 1), lekin asosiy so'rov jadvaldan o'qiganda eski qiymatni ko'rdi: f. Yangi qiymatni ko'rishning yagona yo'li — CTE'ning RETURNING natijasidan o'qish. Snapshot nima ekanini MVCC: Postgres ichida versiyalar darsida chuqur o'rganamiz.
Yana ikki qoida (PostgreSQL hujjatidan):
- Ma'lumot o'zgartiruvchi CTE asosiy so'rovda ishlatilmasa ham bir marta to'liq bajariladi.
- Bitta buyruqda bitta qatorni ikki CTE'da o'zgartirmang — natija oldindan aytib bo'lmaydi.
Tekshirib ko'ring: arxivlash misolida
INSERT INTO t26_arxiv SELECT * FROM vazifalar WHERE bajarildi AND bajarilgan_vaqt < '2026-10-05'(ya'nikochdiemas, jadvalning o'zidan) desak, nima bo'ladi?
Javob
Natija bir xil 7 qator bo'ladi — snapshot qoidasi tufayli asosiy so'rov o'chirilgan qatorlarni hali "ko'radi". Lekin bu xavfli odat: shart CTE va asosiy qismda ikki joyda yoziladi va bir-biridan farq qilib qolishi mumkin. To'g'ri yo'l — RETURNING natijasini (kochdi) ishlatish: "aynan o'chirilganlar arxivlanadi".
4. MATERIALIZED va NOT MATERIALIZED
4.1 CTE optimizatsiya to'sig'i edimi?
PostgreSQL 11 gacha har CTE alohida, bir marta hisoblanar va natijasi vaqtinchalik saqlanar edi. Bu "optimizatsiya to'sig'i" edi: tashqi WHERE id = 5 CTE ichiga o'tmas, 2 mln qatorli jadval to'liq o'qilardi. Eski maqolalarda "CTE sekin" degan gap shundan.
PostgreSQL 12 dan qoida o'zgardi: CTE bir marta ishlatilsa va yon ta'siri bo'lmasa, Postgres uni asosiy so'rovga "eritadi" (inline) — xuddi oddiy ichki so'rov kabi. Kanonda sinaldi (PostgreSQL 18.6, EXPLAIN (COSTS OFF) — narxsiz reja):
EXPLAIN (COSTS OFF)
WITH v AS (SELECT * FROM vazifalar) SELECT * FROM v WHERE id = 5; QUERY PLAN
-----------------------
Seq Scan on vazifalar
Filter: (id = 5)
(2 rows)EXPLAIN (COSTS OFF)
WITH v AS MATERIALIZED (SELECT * FROM vazifalar)
SELECT * FROM v WHERE id = 5; QUERY PLAN
-------------------------------
CTE Scan on v
Filter: (id = 5)
CTE v
-> Seq Scan on vazifalar
(4 rows)Birinchi rejada CTE yo'q — filtr to'g'ridan vazifalar ga tushdi. Ikkinchisida avval butun vazifalar CTE'ga yig'ildi (CTE v), keyin undan filtrlanmoqda (CTE Scan).
Halol eslatma: birinchi rejada ham Seq Scan (butun jadvalni o'qish), indeks emas. Sababi — jadval kichik (47 qator), Postgres uni to'liq o'qishni arzonroq deb hisobladi. Katta jadvalni taqlid qilish uchun to'liq o'qishni taqiqlab ko'ramiz (enable_seqscan = off — faqat sinov uchun sozlama). Biz ishga tushirdik, PostgreSQL 17.6 — ikki rejaning birinchi qatorlarini solishtiring:
SET enable_seqscan = off;
EXPLAIN (COSTS OFF)
WITH v AS (SELECT * FROM vazifalar) SELECT * FROM v WHERE id = 5;
EXPLAIN (COSTS OFF)
WITH v AS MATERIALIZED (SELECT * FROM vazifalar)
SELECT * FROM v WHERE id = 5;SET
QUERY PLAN
---------------------------------------------------------
Index Scan using vazifalar_id_egasi_id_key on vazifalar
Index Cond: (id = 5)
(2 rows)
QUERY PLAN
-------------------------------
CTE Scan on v
Filter: (id = 5)
CTE v
-> Seq Scan on vazifalar
(4 rows)Eritilgan CTE indeks bilan bitta qatorni topdi. MATERIALIZED esa taqiqqa qaramay butun jadvalni o'qidi — filtr CTE ichiga o'ta olmaydi. 2 mln qatorda bu farq millisekund va soniyalar farqi bo'ladi (Indeks nima).
4.2 Qachon qaysi biri
| Holat | Postgres sukuti | Qachon o'zgartirasiz |
|---|---|---|
| CTE bir marta ishlatilgan | eritiladi | MATERIALIZED — ichida qimmat funksiya bo'lib, uni bir marta hisoblash kerak bo'lsa |
| CTE ikki+ marta ishlatilgan | bir marta hisoblanadi | NOT MATERIALIZED — har ishlatishda indeks bilan tezroq bo'lsa |
| Ma'lumot o'zgartiruvchi CTE | doim alohida | o'zgartirib bo'lmaydi |
Amaliy qoida: avval yozing, sukutga ishoning. Sekin bo'lsa — EXPLAIN ga qarang (EXPLAIN ANALYZE'ni o'qish), keyin kalit so'zni qo'shing.
4.3 CTE, ichki so'rov yoki vaqtinchalik jadval?
Oraliq natijani saqlashning uch yo'lini bildingiz. Qaysi birini tanlash — natija qancha "yashashi" kerakligiga bog'liq:
| Vosita | Qancha yashaydi | Qachon |
|---|---|---|
| Ichki so'rov | bitta ifoda ichida | qisqa, bir martalik shart |
CTE (WITH) |
bitta buyruq ichida | ko'p bosqichli so'rov, o'qilishi muhim |
TEMP TABLE |
seans tugaguncha | natija bir nechta buyruqda kerak, indeks kerak |
Ichki so'rov va eritilgan CTE ko'pincha bir xil rejaga aylanadi — ular orasidagi tanlov uslub masalasi. Jamoalarning ko'pi ikki darajadan chuqur ichma-ich so'rovni review'da qaytaradi va CTE'ni so'raydi. Vaqtinchalik jadval esa haqiqiy jadval: unga indeks qo'shish, statistikasini yig'ish mumkin. Lekin u alohida buyruqlar va diskka yozuv talab qiladi.
Tekshirib ko'ring:
WITH s AS (SELECT * FROM vazifalar)CTE'si so'rovda ikki marta ishlatilgan, tashqarida esaWHERE id = 5vaWHERE id = 7bor. Postgres CTE'ni eritadimi? Indeks ishlaydimi?
Javob
Sukut bo'yicha eritmaydi. Ikki marta ishlatilgan CTE bir marta to'liq hisoblanadi, keyin ikki joydan o'qiladi. Natijada butun jadval o'qiladi, indeks ishlamaydi. WITH s AS NOT MATERIALIZED (...) desangiz, ikkala joyga alohida eritiladi va har biri indeks bilan bitta qatorni topadi. Kichik jadvalda farq yo'q, kattasida — katta.
Doimiy nomlangan so'rov kerak bo'lsa (masalan "faol vazifalar" har joyda), unga ko'rinish (view) yaratiladi. Uni Soft delete, timestamps va tarix jadvallari darsida faol_vazifalar misolida ko'ramiz.
5. Ko'p uchraydigan xatolar
5.1 CTE keyingi buyruqda yo'q
WITH s AS (SELECT 1 AS x);
SELECT * FROM s;ERROR: syntax error at or near ";"
LINE 1: WITH s AS (SELECT 1 AS x);
^
ERROR: relation "s" does not exist
LINE 1: SELECT * FROM s;
^Ikki xato. Birinchisi: WITH dan keyin asosiy so'rov bo'lishi shart — CTE yolg'iz turolmaydi. Ikkinchisi: relation "s" does not exist ("s munosabati mavjud emas") — CTE faqat o'z buyrug'i ichida yashaydi. Ko'p buyruqda kerak bo'lsa — vaqtinchalik jadval (CREATE TEMP TABLE) yoki ko'rinish (view) — Soft delete, timestamps va tarix jadvallari darsida.
5.2 Ustunlar soni mos emas
WITH t(a, b) AS (SELECT 1) SELECT * FROM t;ERROR: WITH query "t" has 1 columns available but 2 columns specified
LINE 1: WITH t(a, b) AS (SELECT 1) SELECT * FROM t;
^WITH nom(ustunlar) — ustunlarga nom berish mumkin, lekin soni ichki SELECT bilan teng bo'lsin.
5.3 CTE'lar orasida vergul yoki WITH takrori
Ikkinchi CTE oldidan yana WITH yozish — sintaksis xatosi. Bitta WITH, CTE'lar vergul bilan.
5.4 O'zgartiruvchi CTE'dan keyin eski qiymat
Snapshot qoidasi: yangi qiymat faqat RETURNING dan. Asosiy so'rovda jadvalni qayta o'qimang.
6. Mashqlar
Mashq fayllari kurs/mashqlar/26/26-cte/ da. Hammasi vazifalar holat 23 da.
1-mashq (oson): ichki so'rovni CTE'ga
Quyidagi so'rovni CTE bilan qayta yozing (natija o'zgarmasin):
SELECT round(avg(soni), 2) AS ortacha
FROM (SELECT egasi_id, count(*) AS soni
FROM vazifalar GROUP BY egasi_id);Yordam: «WITH asoslari» bo'limi — ichki so'rov WITH nom AS (...) ga ko'chadi.
Yechim
WITH soni AS (
SELECT egasi_id, count(*) AS soni FROM vazifalar GROUP BY egasi_id
)
SELECT round(avg(soni), 2) AS ortacha FROM soni; ortacha
---------
6.71
(1 row)CTE nomi va ustun nomi bir xil (soni) — Postgres ularni farqlaydi: FROM soni — jadval, avg(soni) — ustun. Lekin o'qish uchun har xil nom yaxshiroq.
2-mashq (o'rta): ikki bosqich
Ikki CTE bilan: (1) har foydalanuvchining yuqori ustuvorlikdagi bajarilmagan vazifalari soni, (2) ulardan 2 va undan ko'plari. Natija: ism, soni. Yordam: 2-bosqich 1-bosqichdan WHERE bilan o'qiydi, keyin foydalanuvchilar bilan JOIN.
Yechim
WITH muhim AS (
SELECT egasi_id, count(*) AS soni FROM vazifalar
WHERE ustuvorlik = 'yuqori' AND NOT bajarildi
GROUP BY egasi_id
), kop AS (
SELECT * FROM muhim WHERE soni >= 2
)
SELECT f.ism, k.soni
FROM kop k JOIN foydalanuvchilar f ON f.id = k.egasi_id
ORDER BY f.id; ism | soni
-----------------+------
Aziz Karimov | 2
Sardor Tursunov | 2
(2 rows)Bu ikki odamda bir nechta "shoshilinch" ish bor — ilovada ularga eslatma ko'rsatish mumkin.
3-mashq (qiyin): yangila va hisobot ber
Azizning (egasi_id = 1) matnida #o'qish bor bajarilmagan vazifalarini bajarilgan qiling (bajarilgan_vaqt = '2026-10-21 18:00+05'). Bitta buyruq natijasi: nechta vazifa yangilandi va ularning id lari vergul bilan. BEGIN ... ROLLBACK ichida. Yordam: UPDATE ... RETURNING li CTE, keyin count(*) va string_agg(id::text, ', ' ORDER BY id).
Yechim
BEGIN;
WITH bajarildi AS (
UPDATE vazifalar SET bajarildi = true,
bajarilgan_vaqt = '2026-10-21 18:00+05'
WHERE egasi_id = 1 AND NOT bajarildi
AND matn LIKE '%#o''qish%'
RETURNING id, matn
)
SELECT count(*) AS soni,
string_agg(id::text, ', ' ORDER BY id) AS idlar
FROM bajarildi;
ROLLBACK;BEGIN
soni | idlar
------+-------
2 | 1, 34
(1 row)
ROLLBACKHisobot RETURNING natijasidan olindi — jadvaldan qayta o'qilganda snapshot qoidasi tufayli eski holat ko'rinardi. bajarilgan_vaqt bilan birga yozildi — vazifalar_bajarilgan_vaqt_check buzilmasin. CTE nomi ustun nomi bilan bir xil (bajarildi) — ruxsat, lekin chalkash; o'zingizda yangilandi deb nomlang.
4-mashq: SQL qadami — ro'yxat va jami soni
25-qism API'si sahifalangan ro'yxat bilan birga jami sonni qaytaradi ("7 tadan 1–3"). Bitta so'rov bilan: Azizning bajarilmagan vazifalaridan eng yangi 3 tasi va har qatorda jami soni. Sxema o'zgarmaydi — commit yo'q. Yordam: CTE royxat, asosiy so'rovda (SELECT count(*) FROM royxat).
Yechim
WITH royxat AS (
SELECT id, matn, yaratilgan_vaqt FROM vazifalar
WHERE egasi_id = 1 AND NOT bajarildi
)
SELECT r.id, r.matn, (SELECT count(*) FROM royxat) AS jami
FROM royxat r
ORDER BY r.yaratilgan_vaqt DESC, r.id DESC
LIMIT 3; id | matn | jami
----+------------------------------------------------+------
38 | Velosiped zanjirini moylash | 7
34 | SQL JOIN mashqlarini yechish #o'qish | 7
23 | Oshxonadagi jo'mrakni almashtirish #ta'mir #uy | 7
(3 rows)royxat ikki marta ishlatildi — Postgres uni bir marta hisoblaydi. LIMIT faqat asosiy so'rovga tegishli, jami esa filtrlangan to'liq to'plamni sanaydi. Window funksiya bilan bundan ham qisqa yo'li bor (count(*) OVER ()) — Window funksiyalar 1 darsida.
7. Real ishda
- Hisobotlar va dashboard so'rovlari deyarli doim CTE bilan yoziladi: 5–10 bosqichli so'rovni review qilish faqat nomlangan bosqichlar bilan mumkin.
- Arxivlash va ko'chirish: "eski loglarni o'chir va arxiv jadvaliga yoz", "buyurtmani bekor qil va omborga qaytar" —
DELETE/UPDATE ... RETURNINGli CTE bitta atomik buyruq. - Navbatlar: ishni olib, belgilab, qaytarish — CTE +
UPDATE ... RETURNING(SKIP LOCKED bilan navbat darsida). - Rekursiv CTE — daraxtlar va graflar uchun CTE'ning maxsus shakli, keyingi dars.
- ORM'lar (Drizzle
$with, Kyselywith) CTE'ni qo'llab-quvvatlaydi (27-qism).
Xulosa
WITH nom AS (...)— so'rov ichidagi nomlangan bosqich; bosqichlar vergul bilan, har biri oldingilarini ko'radi.- CTE faqat o'z buyrug'i ichida yashaydi va bir necha marta ishlatilishi mumkin.
DELETE/UPDATE/INSERT ... RETURNINGli CTE — bir nechta o'zgarish bitta atomik buyruqda.- Bitta
WITHhamma qismlari bir snapshot'ni ko'radi — yangi qiymat faqatRETURNINGdan. - PG 12+: bir marta ishlatilgan CTE eritiladi (indeks ishlaydi);
MATERIALIZED— alohida hisoblashga majburlash.
Keyingi dars: Rekursiv CTE — WITH RECURSIVE bilan kichik vazifalar daraxtini bitta so'rovda aylanib chiqamiz.
Manbalar
- PostgreSQL 18 hujjati — WITH Queries (Common Table Expressions): https://www.postgresql.org/docs/18/queries-with.html
- PostgreSQL 12 reliz eslatmalari (CTE inlining, MATERIALIZED): https://www.postgresql.org/docs/12/release-12.html
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!