IlmHamroh
JavaScript Full-stack/26-qism. SQL va PostgreSQL26/64-dars17 daqiqa
Mundarija (27)

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 ichida INSERT/UPDATE/DELETE ... RETURNING ham bo'ladi — masalan qatorlarni bitta buyruqda o'chirib arxivga ko'chirish. PostgreSQL 12 dan beri bir marta ishlatilgan CTE asosiy so'rovga "eritiladi"; MATERIALIZED bilan alohida hisoblashga majburlash mumkin.

Bu darsda

  • WITH bilan bir nechta nomlangan bosqichli so'rov yozasiz.
  • Bitta CTE natijasini so'rovda bir necha marta ishlatasiz.
  • DELETE ... RETURNING li CTE bilan qatorlarni arxivga ko'chirasiz va "bitta snapshot" qoidasini tushuntirasiz.
  • MATERIALIZED va NOT MATERIALIZED farqini EXPLAIN da 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:

  1. Har foydalanuvchining statistikasini hisobla.
  2. Ulardan faollarini (5+ vazifa) ajrat.
  3. 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):

sql
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;
text
       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"] --> A

Diagramma — 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):

sql
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;
text
       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):

sql
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;
text
       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: faollar CTE'sini statistika dan 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:

sql
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;
text
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)

ROLLBACK

DELETE ... 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):

sql
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;
text
BEGIN
 id | egasi_id |                matn
----+----------+-------------------------------------
 48 |        9 | IlmHamroh'da birinchi darsni o'qish
(1 row)

INSERT 0 1
ROLLBACK

yangi 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:

sql
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;
text
BEGIN
 asosiy_soroq_korgani | yangilandi
----------------------+------------
 f                    |          1
(1 row)

ROLLBACK

CTE 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'ni kochdi emas, 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):

sql
EXPLAIN (COSTS OFF)
WITH v AS (SELECT * FROM vazifalar) SELECT * FROM v WHERE id = 5;
text
      QUERY PLAN
-----------------------
 Seq Scan on vazifalar
   Filter: (id = 5)
(2 rows)
sql
EXPLAIN (COSTS OFF)
WITH v AS MATERIALIZED (SELECT * FROM vazifalar)
SELECT * FROM v WHERE id = 5;
text
          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:

sql
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;
text
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 esa WHERE id = 5 va WHERE id = 7 bor. 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

sql
WITH s AS (SELECT 1 AS x);
SELECT * FROM s;
text
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

sql
WITH t(a, b) AS (SELECT 1) SELECT * FROM t;
text
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):

sql
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
sql
WITH soni AS (
  SELECT egasi_id, count(*) AS soni FROM vazifalar GROUP BY egasi_id
)
SELECT round(avg(soni), 2) AS ortacha FROM soni;
text
 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
sql
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;
text
       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
sql
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;
text
BEGIN
 soni | idlar
------+-------
    2 | 1, 34
(1 row)

ROLLBACK

Hisobot 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
sql
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;
text
 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 ... RETURNING li 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, Kysely with) 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 ... RETURNING li CTE — bir nechta o'zgarish bitta atomik buyruqda.
  • Bitta WITH hamma qismlari bir snapshot'ni ko'radi — yangi qiymat faqat RETURNING dan.
  • 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

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
SQL CTE (WITH): murakkab so'rovni nomlangan bosqichlarga bo'lish — IlmHamroh