CTE — WITH, rekursiv CTE, materialized CTE
Muammodan boshlaylik
Murakkab so'rov yozasiz: avval foydalanuvchilarni filtrlab, ularning buyurtma statistikasini hisoblaysiz, keyin shu statistikadan yana filtraysiz. Subquery bilan yozilsa — so'rov ichida so'rov, u ichida yana so'rov. O'qiganda qayerdan boshlashni bilmaysiz.
SELECT ism, jami FROM (
SELECT f.ism, SUM(b.summa) AS jami
FROM (SELECT * FROM foydalanuvchilar WHERE shahar = 'Toshkent') AS f
JOIN buyurtmalar b ON f.id = b.foydalanuvchi_id
GROUP BY f.id, f.ism
) AS stats
WHERE jami > 1000000;
Ishlaydi — lekin o'qish qiyin. Ichidan tashqariga qarab tushunish kerak.
CTE bu muammoni hal qiladi: murakkab so'rovni qadamma-qadam, nom berib yozish.
CTE nima?
CTE (Common Table Expression) — WITH kalit so'zi bilan belgilanadigan vaqtinchalik nomlangan so'rov. U asosiy so'rovdan oldin yoziladi va o'sha so'rov ichida oddiy jadval kabi ishlatiladi.
Hayotiy o'xshatma: oshpaz va retsept. Murakkab taom pishirishda oshpaz har bir qadamni alohida tayyorlaydi — avval sous, alohida go'sht, alohida sabzavotlar. Keyin barchasini birlashtiradi. CTE — shu "alohida tayyorlash" qadamlari. Har birining nomi bor, birini boshqasida ishlatish mumkin.
WITH
qadam1 AS (...), ← birinchi tayyor
qadam2 AS (...), ← qadam1 dan foydalanishi mumkin
qadam3 AS (...) ← qadam1, qadam2 dan foydalanishi mumkin
SELECT ... FROM qadam3;
Asosiy CTE
Yuqoridagi murakkab subquery CTE bilan:
WITH
toshkentliklar AS (
SELECT id, ism
FROM foydalanuvchilar
WHERE shahar = 'Toshkent'
),
statistika AS (
SELECT
t.ism,
SUM(b.summa) AS jami
FROM toshkentliklar t
JOIN buyurtmalar b ON t.id = b.foydalanuvchi_id
GROUP BY t.id, t.ism
)
SELECT ism, jami
FROM statistika
WHERE jami > 1000000;
Natija bir xil — lekin o'qish ancha oson. Har bir qadam nomi bilan ajratilgan, qaysi qadam nima qilayotgani tushunish mumkin.
toshkentliklar → Toshkentdagi foydalanuvchilarni ajratadi
statistika → ularning buyurtma summalarini hisoblaydi
asosiy so'rov → 1 000 000 dan yuqorilarini filtrlab chiqaradi
CTE ni qachon ishlatish kerak?
CTE har doim ham kerak emas. Oddiy so'rovda CTE ortiqcha. Quyidagi hollarda foydali:
So'rov bir necha bosqichdan iborat bo'lganda — har bir bosqichni nom bilan ajratish o'qilishni osonlashtiradi.
Bir xil subquery bir necha joyda kerak bo'lganda — CTE bir marta yoziladi, bir necha joyda ishlatiladi:
WITH eng_yaxshi AS (
SELECT foydalanuvchi_id
FROM buyurtmalar
GROUP BY foydalanuvchi_id
HAVING SUM(summa) > 5000000
)
SELECT f.ism, b.mahsulot
FROM foydalanuvchilar f
JOIN buyurtmalar b ON f.id = b.foydalanuvchi_id
JOIN eng_yaxshi ey ON f.id = ey.foydalanuvchi_id;
-- ↑ CTE ikki joyda ishlatilishi mumkin edi
Rekursiv CTE
Bu CTE ning eng kuchli tomoni. Rekursiv CTE — o'z ichida o'zini chaqiradi. Ierarxik ma'lumotlar uchun — daraxt tuzilmalar, kategoriyalar, xodim-rahbar zanjiri.
Avval tuzilmani tasavvur qiling:
Direktor (id=1, rahbar_id=NULL)
└── Bo'lim boshlig'i (id=2, rahbar_id=1)
├── Xodim A (id=3, rahbar_id=2)
└── Xodim B (id=4, rahbar_id=2)
└── Stajyor (id=5, rahbar_id=4)
Bu ma'lumot bir jadvalda saqlanadi:
CREATE TABLE xodimlar (
id INT PRIMARY KEY,
ism TEXT,
rahbar_id INT REFERENCES xodimlar(id)
);
INSERT INTO xodimlar VALUES
(1, 'Kamol', NULL),
(2, 'Jasur', 1),
(3, 'Malika', 2),
(4, 'Sherzod', 2),
(5, 'Dilnoza', 4);
Direktordan boshlab barcha qo'l ostidagilarni topish — oddiy JOIN bilan imkonsiz, chunki qancha daraja chuqur ekanini bilmaymiz.
Rekursiv CTE:
WITH RECURSIVE xodimlar_daraxti AS (
-- Boshlang'ich qism: direktorni olamiz
SELECT id, ism, rahbar_id, 0 AS daraja
FROM xodimlar
WHERE rahbar_id IS NULL
UNION ALL
-- Rekursiv qism: har bir xodimning qo'l ostidagilarini qo'shamiz
SELECT x.id, x.ism, x.rahbar_id, xd.daraja + 1
FROM xodimlar x
JOIN xodimlar_daraxti xd ON x.rahbar_id = xd.id
-- ↑ CTE ning o'ziga murojaat
)
SELECT
REPEAT(' ', daraja) || ism AS tuzilma, -- ← darajaga qarab bo'shliq
daraja
FROM xodimlar_daraxti
ORDER BY daraja, ism;
tuzilma │ daraja
--------------------+--------
Kamol │ 0
Jasur │ 1
Malika │ 2
Sherzod │ 2
Dilnoza │ 3
Rekursiv CTE qanday ishlaydi:
1-bosqich: Kamol (daraja=0) ← boshlang'ich qism
2-bosqich: Jasur (daraja=1) ← Kamolning qo'l ostidag'i
3-bosqich: Malika, Sherzod (2) ← Jasurning qo'l ostidagilar
4-bosqich: Dilnoza (3) ← Sherzodning qo'l ostidag'i
5-bosqich: natija yo'q → to'xtaydi
Rekursiv CTE tuzilmasi
WITH RECURSIVE cte_nomi AS (
-- 1. Boshlang'ich qism (bir marta ishlaydi)
SELECT ...
FROM jadval
WHERE boshlang'ich_shart
UNION ALL
-- 2. Rekursiv qism (natija bo'lguncha ishlaydi)
SELECT ...
FROM jadval
JOIN cte_nomi ON bog'lanish_sharti -- ← o'ziga murojaat
)
SELECT * FROM cte_nomi;
UNION ALL — ikki qismni birlashtiradi. Rekursiv qism bo'sh natija qaytarguncha davom etadi.
⚠️ Diqqat: Rekursiv CTE cheksiz aylanib qolishi mumkin — agar tsikl bo'lsa (A → B → A). PostgreSQL standart holatda 100 daraja chuqurlikdan keyin to'xtatadi.
SET max_recursion_depthbilan o'zgartirish mumkin.
Materialized CTE
Oddiy CTE — ba'zan PostgreSQL uni optimize qilib, har safar qayta hisoblaydi. MATERIALIZED kalit so'zi CTE natijasini bir marta hisoblash va keshda saqlashni buyuradi.
WITH katta_buyurtmalar AS MATERIALIZED (
SELECT * FROM buyurtmalar WHERE summa > 1000000
)
SELECT * FROM katta_buyurtmalar WHERE mahsulot LIKE 'N%';
MATERIALIZED qachon foydali:
CTE bir necha joyda ishlatilganda → bir marta hisob, ko'p marta foydalanish
Ichki so'rov og'ir hisoblashda → natija keshlanadi
NOT MATERIALIZED — aksincha, PostgreSQL ni optimize qilishga ruxsat beradi:
WITH oddiy AS NOT MATERIALIZED (
SELECT * FROM foydalanuvchilar
)
SELECT * FROM oddiy WHERE id = 1;
-- PostgreSQL bu yerda CTE ni inline qilib, indeks ishlatishi mumkin
CTE va subquery — qaysi biri?
Subquery yaxshi:
→ Oddiy, bir martalik filtr
→ So'rov qisqa va tushunish oson
CTE yaxshi:
→ Ko'p bosqichli so'rov
→ Bir xil subquery bir necha joyda kerak
→ Rekursiv ma'lumotlar (daraxt, ierarxiya)
→ So'rovni qadamma-qadam tushuntirish kerak
Ikkalasi ko'pincha bir xil natija beradi. CTE — o'qilishi oson, subquery — ba'zan tezroq (PostgreSQL optimizatsiyasi tufayli).
Tez-tez uchraydigan xatolar
Rekursiv CTE da RECURSIVE kalit so'zini unutish:
-- ❌ — RECURSIVE yo'q — xato beradi
WITH daraxt AS (
SELECT id FROM xodimlar WHERE rahbar_id IS NULL
UNION ALL
SELECT x.id FROM xodimlar x JOIN daraxt d ON x.rahbar_id = d.id
)
-- ✅
WITH RECURSIVE daraxt AS ( ... )
RECURSIVE bo'lmasa PostgreSQL CTE ni oddiy deb hisoblaydi — o'z ichida o'ziga murojaat qilishga ruxsat bermaydi.
Cheksiz rekursiya:
-- ❌ — ma'lumotda tsikl bo'lsa (id=1 rahbari id=2, id=2 rahbari id=1)
-- PostgreSQL 100 darajadan keyin xato beradi:
-- ERROR: infinite recursion detected in policy for relation
Ishonchli bo'lish uchun chuqurlikni cheklash:
WITH RECURSIVE daraxt AS (
SELECT id, 0 AS daraja FROM xodimlar WHERE rahbar_id IS NULL
UNION ALL
SELECT x.id, d.daraja + 1
FROM xodimlar x JOIN daraxt d ON x.rahbar_id = d.id
WHERE d.daraja < 10 -- ← 10 darajadan chuqurga tushmasin
)
Xulosa
CTE — murakkab so'rovni o'qilishi oson qadamlarga bo'lish usuli.
WITH
1_qadam AS (...),
2_qadam AS (...),
SELECT ... FROM 2_qadam;
Uch tur:
| Tur | Qachon |
|---|---|
| Oddiy CTE | Ko'p bosqichli so'rov, takroriy subquery |
| Rekursiv CTE | Daraxt, ierarxiya, cheksiz chuqurlikdagi ma'lumotlar |
| Materialized CTE | CTE bir necha joyda — bir marta hisoblash |
CTE so'rovni tushunarsiz subquery uyumidan tushunarli qadamlarga aylantiradi. Ayniqsa rekursiv CTE — ierarxik ma'lumotlarsiz muqobili yo'q.
Keyingi dars
18-dars: Window Functions — ROW_NUMBER, RANK, DENSE_RANK, NTILE