Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Tuzilma va bajarilish
- 2.1 Ikki qism
- 2.2 Qadamma-qadam
- 2.3 Har qadam — bitta JOIN
- 3. Butun daraxt: chuqurlik, yo'l va chekinish
- 3.1 Bir nechta ildiz
- 3.2 SEARCH bandi (PG 14+)
- 4. Pastdan yuqoriga
- 5. Rekursiv natija bilan hisob
- 6. Sikl va cheksiz rekursiya
- 6.1 Siklli ma'lumot
- 6.2 CYCLE bandi (PG 14+)
- 6.3 UNION va UNION ALL
- 7. Rekursiya — faqat daraxt uchun emas
- 8. Ko'p uchraydigan xatolar
- 8.1 RECURSIVE so'zi unutildi
- 8.2 Turlar mos emas
- 8.3 So'rov tugamayapti
- 8.4 Daraxt tartibsiz chiqdi
- 9. Mashqlar
- 1-mashq (oson): 1 dan 10 gacha
- 2-mashq (o'rta): Azizning portfolio ishi
- 3-mashq (qiyin): barglardan ildizgacha yo'l
- 4-mashq: SQL qadami — o'chirishdan oldin ogohlantirish
- 10. Real ishda
- Xulosa
- Manbalar
SQL rekursiv CTE: WITH RECURSIVE bilan daraxt va graflarni aylanib chiqish
Qisqacha: Rekursiv CTE (
WITH RECURSIVE) — o'zini o'zi chaqiradigan CTE. U ikki qismdan iborat: anchor (boshlang'ich qatorlar, masalan daraxt ildizi) vaUNION ALLdan keyingi rekursiv qism (oldingi qadamda topilganlarning "farzandlari"). Postgres yangi qator chiqmay qolguncha qadamlarni takrorlaydi. Shu bilan kichik vazifalar, kategoriyalar, izohlar zanjiri kabi har qanday chuqurlikdagi daraxt bitta so'rovda o'qiladi. Siklli ma'lumotda cheksiz aylanishdanCYCLEbandi (PG 14+) yoki chuqurlik chegarasi himoya qiladi.
Bu darsda
WITH RECURSIVEning ikki qismini va qadamma-qadam bajarilishini tushuntirasiz.- Daraxtni yuqoridan pastga chuqurlik, yo'l massivi va chekinish bilan chiqarasiz.
- Pastdan yuqoriga — vazifaning hamma "ota-bobolari"ni topasiz.
CYCLE,SEARCHbandlari va chuqurlik chegarasi bilan cheksiz sikldan himoyalanasiz.- Vazifa o'chirilganda u bilan birga nechta kichik vazifa ketishini hisoblaysiz.
Oldin bilishingiz kerak: CTE (WITH), JOIN'lar (self-join), Rekursiya asoslari.
1. Nega bu kerak?
vazifalar jadvalida ota_id ustuni bor: kichik vazifa o'z otasini ko'rsatadi. Madinaning kurs ishi shunday ko'rinadi:
flowchart TD
A["7: Kurs ishi"] --> B["41: Jadvallarni loyihalash"]
A --> C["42: Adabiyotlar ro'yxati"]
B --> D["46: ER diagramma chizish"]
B --> E["47: Normalizatsiyani tekshirish"]Diagrammada uch daraja bor: kurs ishi, uning qadamlari va qadamning qadamlari. JOIN'lar darsidagi self-join bitta darajani ko'rardi: "46 ning otasi 41". 7 ning hamma avlodlarini topish uchun self-join'ni necha marta yozish kerak? Ikki marta — bugun. Ertaga Madina to'rtinchi daraja qo'shsa — uch marta. Chuqurlik oldindan noma'lum, so'rov esa qat'iy.
Dasturlashda bu masala tanish: Rekursiya asoslari darsida funksiya o'zini chaqirib, daraxtsimon ma'lumotni aylanib chiqqan edi. SQL'da xuddi shu g'oya — rekursiv CTE. O'xshatish: oila shajarasi. "Bobomning hamma avlodlari" ni topish uchun avval bobomning farzandlarini yozasiz, keyin ularning farzandlarini, keyin ularnikini — yangi ism chiqmay qolguncha.
2. Tuzilma va bajarilish
2.1 Ikki qism
WITH RECURSIVE daraxt AS (
SELECT id, matn, ota_id, 1 AS chuqurlik
FROM vazifalar WHERE id = 7
UNION ALL
SELECT v.id, v.matn, v.ota_id, d.chuqurlik + 1
FROM vazifalar v JOIN daraxt d ON v.ota_id = d.id
)
SELECT id, matn, chuqurlik FROM daraxt ORDER BY chuqurlik, id; id | matn | chuqurlik
----+-----------------------------------------------+-----------
7 | Kurs ishi: ma'lumotlar bazasi bo'limi #o'qish | 1
41 | Jadvallarni loyihalash | 2
42 | Adabiyotlar ro'yxati | 2
46 | ER diagramma chizish | 3
47 | Normalizatsiyani tekshirish | 3
(5 rows)Biz ishga tushirdik (PostgreSQL 17.6, vazifalar holat 23). Qismlar:
| Qism | Vazifasi |
|---|---|
WITH RECURSIVE daraxt |
CTE o'ziga murojaat qila oladi |
1-SELECT (anchor) |
boshlang'ich qator: 7-vazifa |
UNION ALL |
qadamlar natijasini qo'shib boradi |
2-SELECT (rekursiv qism) |
daraxt dagi qatorlarning farzandlari |
Anchor (langar) — rekursiya boshlanadigan nuqta, o'ziga murojaat qilmaydi. Rekursiv qism daraxt ni o'zi ishlatadi: JOIN daraxt d ON v.ota_id = d.id — "otasi daraxtda bo'lgan vazifalar". chuqurlik + 1 — har qadamda bir daraja pastga.
2.2 Qadamma-qadam
Nom "rekursiv" bo'lsa ham, Postgres buni aslida sikl bilan bajaradi. U ishchi jadval (working table) yuritadi — oxirgi qadamda topilgan qatorlar:
| Qadam | Ishchi jadval (yangi topilganlar) | Natijada jami |
|---|---|---|
| 1 (anchor) | 7 | 1 |
| 2 | 41, 42 (otasi 7) | 3 |
| 3 | 46, 47 (otasi 41 yoki 42) | 5 |
| 4 | — (otasi 46 yoki 47 bo'lgan yo'q) | to'xtadi |
Muhim nuqta: rekursiv qism har qadamda faqat oldingi qadam topganlarini ko'radi, butun natijani emas. 3-qadamda daraxt = {41, 42}, 7 emas. Shuning uchun 41 va 42 qayta topilmaydi. Ishchi jadval bo'sh qolganda sikl tugaydi.
flowchart LR
A["anchor:<br/>7"] --> W1["1-qadam:<br/>41, 42"]
W1 --> W2["2-qadam:<br/>46, 47"]
W2 --> W3["3-qadam:<br/>bo'sh"]
W3 --> R["natija:<br/>5 qator"]Diagramma — sikl iteratsiyalari: har quti oldingisidan "farzandlar" ni oladi. Bo'sh quti — to'xtash sharti.
2.3 Har qadam — bitta JOIN
Har iteratsiyada Postgres bitta oddiy JOIN bajaradi: "ishchi jadvaldagi id'lar — vazifalar.ota_id da". Demak ota_id bo'yicha qidiruv tez bo'lishi kerak. ALTER TABLE darsida vazifalar_ota_id_idx indeksini aynan shuning uchun qo'shgan edik. U faqat ota_id IS NOT NULL qatorlarni saqlaydi (partial indeks): ko'p vazifaning otasi yo'q, ularni indeksga yozish behuda.
Daraxt chuqurligi 3 bo'lsa — 3–4 ta JOIN, 10 bo'lsa — 10–11 ta. Qatorlar soni esa faqat shu daraxtdagi tugunlar soniga bog'liq, butun jadvalga emas. 2 mln vazifali bazada ham bitta loyihaning daraxti millisekundlarda o'qiladi — indeks bor bo'lsa.
Tekshirib ko'ring: anchor'da
WHERE id = 7o'rnigaWHERE id = 41yozsak, natijada nechta qator bo'ladi?
Javob
3 ta: 41 (anchor), keyin uning farzandlari 46 va 47. Ularning farzandi yo'q — to'xtaydi. Rekursiya faqat pastga yuradi, 41 ning otasi (7) va "akasi" (42) chiqmaydi.
3. Butun daraxt: chuqurlik, yo'l va chekinish
3.1 Bir nechta ildiz
Anchor bitta qator bo'lishi shart emas. Madinaning hamma yuqori darajadagi vazifalari (otasi yo'qlar) — ildizlar. Kanon tajribasi (PostgreSQL 18.6):
WITH RECURSIVE daraxt AS (
SELECT id, matn, ota_id, 1 AS chuqurlik,
ARRAY[id] AS yol
FROM vazifalar WHERE ota_id IS NULL AND egasi_id = 4
UNION ALL
SELECT v.id, v.matn, v.ota_id, d.chuqurlik + 1,
d.yol || v.id
FROM vazifalar v JOIN daraxt d ON v.ota_id = d.id
)
SELECT repeat(' ', chuqurlik - 1) || matn AS daraxt,
chuqurlik, yol
FROM daraxt ORDER BY yol; daraxt | chuqurlik | yol
-----------------------------------------------+-----------+-----------
Kurs ishi: ma'lumotlar bazasi bo'limi #o'qish | 1 | {7}
Jadvallarni loyihalash | 2 | {7,41}
ER diagramma chizish | 3 | {7,41,46}
Normalizatsiyani tekshirish | 3 | {7,41,47}
Adabiyotlar ro'yxati | 2 | {7,42}
Kutubxonaga kitob qaytarish #o'qish | 1 | {13}
Yotoqxona to'lovi #to'lov | 1 | {18}
Python laboratoriya ishi #o'qish | 1 | {31}
Uyga qo'ng'iroq qilish #oila | 1 | {37}
(9 rows)Kanon so'rovida yana yol_matni ustuni bor edi (ota matnlari zanjiri) — bu yerda qisqartirish uchun olib tashladik, natija o'zgarmaydi. Uchta yangi narsa:
yol(yo'l) — ildizdan shu qatorgacha bo'lgan id'lar massivi. Anchor'daARRAY[id], har qadamdad.yol || v.id— massiv oxiriga qo'shish.ORDER BY yol— massivlar element-element solishtiriladi:{7}<{7,41}<{7,41,46}<{7,42}. Natija — har ota darhol farzandlari bilan, xuddi papkalar daraxtidek.repeat(' ', chuqurlik - 1)— chuqurlikka qarab bo'sh joy qo'yadi: ekranda chekinish (indentatsiya) bilan daraxt.
3.2 SEARCH bandi (PG 14+)
Yo'l massivini qo'lda yasash o'rniga, SQL standartidagi SEARCH bandi tartib ustunini o'zi yaratadi. Ikki rejim bor: DEPTH FIRST (avval chuqurga — yuqoridagi ORDER BY yol kabi) va BREADTH FIRST (avval kengga — daraja-daraja). Biz sinadik (PostgreSQL 17.6):
WITH RECURSIVE daraxt AS (
SELECT id, matn, ota_id FROM vazifalar WHERE id = 7
UNION ALL
SELECT v.id, v.matn, v.ota_id
FROM vazifalar v JOIN daraxt d ON v.ota_id = d.id
) SEARCH DEPTH FIRST BY id SET tartib
SELECT id, matn, tartib FROM daraxt ORDER BY tartib; id | matn | tartib
----+-----------------------------------------------+-----------------
7 | Kurs ishi: ma'lumotlar bazasi bo'limi #o'qish | {(7)}
41 | Jadvallarni loyihalash | {(7),(41)}
46 | ER diagramma chizish | {(7),(41),(46)}
47 | Normalizatsiyani tekshirish | {(7),(41),(47)}
42 | Adabiyotlar ro'yxati | {(7),(42)}
(5 rows)tartib ustunini Postgres o'zi qo'shdi — u bizning yol ning o'zi (har element qavsda, chunki BY dan keyin bir nechta ustun bo'lishi mumkin). BREADTH FIRST bilan tartib 7, 41, 42, 46, 47 bo'ladi — "Qadamma-qadam" jadvalidagi kabi.
4. Pastdan yuqoriga
Teskari savol: "ER diagramma chizish" qaysi katta ishning qismi? Endi anchor — barg (pastdagi tugun), rekursiv qism esa otasini topadi (v.id = y.ota_id):
WITH RECURSIVE yuqoriga AS (
SELECT id, matn, ota_id FROM vazifalar
WHERE matn = 'ER diagramma chizish'
UNION ALL
SELECT v.id, v.matn, v.ota_id
FROM vazifalar v JOIN yuqoriga y ON v.id = y.ota_id
)
SELECT * FROM yuqoriga; id | matn | ota_id
----+-----------------------------------------------+--------
46 | ER diagramma chizish | 41
41 | Jadvallarni loyihalash | 7
7 | Kurs ishi: ma'lumotlar bazasi bo'limi #o'qish |
(3 rows)Uch qadam: 46 → otasi 41 → otasi 7 → 7 ning otasi NULL, JOIN hech narsa topmadi — to'xtadi. Yuqoriga yo'l doim bitta zanjir (har vazifaning bitta otasi), pastga esa tarmoqlanadi. Bu so'rov ilovada "non" yo'li (breadcrumbs) uchun kerak: "Kurs ishi › Jadvallarni loyihalash › ER diagramma chizish".
5. Rekursiv natija bilan hisob
Har yuqori darajadagi vazifaning hamma darajadagi kichik vazifalari soni va ulardan nechtasi bajarilgan. Asosiy hiyla: rekursiv CTE ildiz ni (qaysi daraxtdan kelganini) har qadamda uzatib boradi:
WITH RECURSIVE avlod AS (
SELECT id AS ildiz, id FROM vazifalar WHERE ota_id IS NULL
UNION ALL
SELECT a.ildiz, v.id
FROM vazifalar v JOIN avlod a ON v.ota_id = a.id
)
SELECT r.matn, count(*) - 1 AS kichiklari,
count(*) FILTER (WHERE v.bajarildi AND v.id <> r.id)
AS bajarilgan
FROM avlod a JOIN vazifalar v ON v.id = a.id
JOIN vazifalar r ON r.id = a.ildiz
GROUP BY r.id, r.matn HAVING count(*) > 1 ORDER BY r.id; matn | kichiklari | bajarilgan
-----------------------------------------------+------------+------------
Kurs ishi: ma'lumotlar bazasi bo'limi #o'qish | 4 | 1
Portfolio saytiga blog yozish #portfolio | 3 | 1
(2 rows)Kanon tajribasi. count(*) - 1 — ildizning o'zini ayirib tashlaymiz. HAVING count(*) > 1 — kichik vazifasi yo'q ildizlar chiqmasin. Ilovada bu "4 tadan 1 tasi bajarildi" ko'rsatkichi bo'ladi.
Tekshirib ko'ring: nega rekursiv qismda
a.ildizbor,v.idemas?
Javob
ildiz — qaysi daraxtga tegishlilik belgisi. U anchor'da bir marta o'rnatiladi (id AS ildiz) va pastga o'zgarmasdan uzatiladi. Agar v.id yozsak, har qadamda yangi qator o'zini ildiz deb hisoblaydi va guruhlash buziladi.
6. Sikl va cheksiz rekursiya
6.1 Siklli ma'lumot
Bizning daraxtda sikl yo'q: vazifalar_ota_id_check (ota_id <> id) o'ziga ota bo'lishni taqiqlaydi. Lekin 1 → 2 → 1 kabi uzunroq sikl baza darajasida taqiqlanmagan. Graflarda (do'stlar tarmog'i, yo'llar xaritasi) sikl esa odatiy hol. Siklli ma'lumotda rekursiya hech qachon to'xtamaydi.
Kanon tajribasida vaqtinchalik t27_graf jadvali: 1 → 2 → 3 → 1. Sukut himoyasiz yozilsa — cheksiz. Birinchi himoya — chuqurlik chegarasi:
CREATE TEMP TABLE t27_graf (id int, keyingi int);
INSERT INTO t27_graf VALUES (1, 2), (2, 3), (3, 1);
WITH RECURSIVE yur AS (
SELECT id, keyingi, 1 AS n FROM t27_graf WHERE id = 1
UNION ALL
SELECT g.id, g.keyingi, y.n + 1
FROM t27_graf g JOIN yur y ON g.id = y.keyingi
WHERE y.n < 7
)
SELECT * FROM yur;CREATE TABLE
INSERT 0 3
id | keyingi | n
----+---------+---
1 | 2 | 1
2 | 3 | 2
3 | 1 | 3
1 | 2 | 4
2 | 3 | 5
3 | 1 | 6
1 | 2 | 7
(7 rows)WHERE y.n < 7 — 7-qadamda to'xtash. Sikl ko'rinib turibdi: 1, 2, 3, 1, 2, 3... Chegara sikldan himoya qiladi, lekin uni "aniqlamaydi" — 7 qadam chuqur daraxt ham kesiladi.
6.2 CYCLE bandi (PG 14+)
CYCLE bandi siklni o'zi aniqlaydi: yo'lni yuritadi va qator yo'lda allaqachon bo'lsa, uni belgilab, davom ettirmaydi:
CREATE TEMP TABLE t27_graf (id int, keyingi int);
INSERT INTO t27_graf VALUES (1, 2), (2, 3), (3, 1);
WITH RECURSIVE yur AS (
SELECT id, keyingi FROM t27_graf WHERE id = 1
UNION ALL
SELECT g.id, g.keyingi
FROM t27_graf g JOIN yur y ON g.id = y.keyingi
) CYCLE id SET siklmi USING yol
SELECT * FROM yur;CREATE TABLE
INSERT 0 3
id | keyingi | siklmi | yol
----+---------+--------+-------------------
1 | 2 | f | {(1)}
2 | 3 | f | {(1),(2)}
3 | 1 | f | {(1),(2),(3)}
1 | 2 | t | {(1),(2),(3),(1)}
(4 rows)CYCLE id SET siklmi USING yol — "id takrorlansa, siklmi ni rost qil, yo'lni yol ustunida saqla". To'rtinchi qatorda 1 qayta keldi — siklmi = t va rekursiya shu tarmoqda to'xtadi. Kanon tajribasi (PostgreSQL 18.6).
PG 14 dan oldin buni qo'lda qilishardi: yol massivini yuritib, rekursiv qismda WHERE NOT g.id = ANY(y.yol) sharti bilan.
6.3 UNION va UNION ALL
UNION ALL o'rniga UNION yozilsa, Postgres takror qatorlarni tashlaydi — oldin chiqqan aynan bir xil qator qayta qo'shilmaydi va sikl to'xtaydi. Lekin bu faqat qatordagi hamma ustunlar takrorlanganda ishlaydi. n yoki yol kabi o'zgaruvchi ustun bo'lsa, qatorlar hech qachon bir xil bo'lmaydi va UNION himoya qilmaydi. Ishonchli yo'l — CYCLE yoki chegara.
Tekshirib ko'ring:
vazifalarda hozir sikl yo'q. Nega baribir ilovada daraxt so'roviga chuqurlik chegarasi (masalanWHERE d.chuqurlik < 20) qo'yish foydali?
Javob
Ikki sabab. Birinchisi: ota_id <> id faqat "o'ziga ota" ni taqiqlaydi; 41 → 46 → 41 kabi uzun sikl xato UPDATE bilan paydo bo'lishi mumkin. Ikkinchisi: juda chuqur daraxt (masalan kimdir skript bilan 100 000 darajali zanjir yaratsa) serverni band qiladi. Chegara — arzon sug'urta. Haqiqiy himoya esa sikl paydo bo'lishiga bazada yo'l qo'ymaslik, masalan trigger bilan. Trigger yozishni Denormalizatsiya va hisoblangan maydonlar darsida o'rganamiz.
Diqqat: himoyasiz rekursiv so'rov serverni to'xtovsiz band qiladi va xotirani to'ldiradi. Ilovada so'rovlarga vaqt chegarasi qo'ying (
SET statement_timeout = '5s') — u haqda Connection pooling va Deadlock va serialization failure bilan ishlash darslarida.
7. Rekursiya — faqat daraxt uchun emas
Rekursiv CTE ketma-ketlik ham yasay oladi. 1-oktyabrdan 5-oktyabrgacha sanalar:
WITH RECURSIVE kunlar AS (
SELECT '2026-10-01'::date AS kun
UNION ALL
SELECT kun + 1 FROM kunlar WHERE kun < '2026-10-05'
)
SELECT * FROM kunlar; kun
------------
2026-10-01
2026-10-02
2026-10-03
2026-10-04
2026-10-05
(5 rows)Bu kanon tajribasi. Postgres'da buning uchun tayyor funksiya bor — generate_series (Tahliliy SQL naqshlari darsida). Rekursiv variant boshqa bazalarda yoki murakkab qoidali ketma-ketlikda (masalan "faqat ish kunlari") kerak bo'ladi.
8. Ko'p uchraydigan xatolar
8.1 RECURSIVE so'zi unutildi
WITH daraxt AS (
SELECT id FROM vazifalar WHERE id = 7
UNION ALL
SELECT v.id FROM vazifalar v JOIN daraxt d ON v.ota_id = d.id
)
SELECT * FROM daraxt;ERROR: relation "daraxt" does not exist
LINE 4: SELECT v.id FROM vazifalar v JOIN daraxt d ON v.ota_id = d...
^
DETAIL: There is a WITH item named "daraxt", but it cannot be referenced from this part of the query.
HINT: Use WITH RECURSIVE, or re-order the WITH items to remove forward references.Tarjimasi: "daraxt munosabati yo'q. Tafsilot: shu nomli WITH elementi bor, lekin so'rovning bu qismidan unga murojaat qilib bo'lmaydi. Maslahat: WITH RECURSIVE ishlating". Oddiy WITH o'ziga murojaatni bilmaydi. RECURSIVE bitta WITH ga bir marta yoziladi, hatto CTE'lardan faqat bittasi rekursiv bo'lsa ham.
8.2 Turlar mos emas
WITH RECURSIVE sonlar AS (
SELECT 1 AS n
UNION ALL
SELECT n + 0.5 FROM sonlar WHERE n < 3
)
SELECT * FROM sonlar;ERROR: recursive query "sonlar" column 1 has type integer in non-recursive term but type numeric overall
LINE 2: SELECT 1 AS n
^
HINT: Cast the output of the non-recursive term to the correct type.Tarjimasi: "rekursiv so'rovning 1-ustuni anchor'da integer, umumiy natijada esa numeric. Maslahat: anchor chiqishini kerakli turga o'giring". Anchor 1 (butun), rekursiv qism n + 0.5 (kasr). Yechim: SELECT 1::numeric AS n. Matnda ham shunday: varchar(100) va text aralashsa — ::text bilan tenglang.
8.3 So'rov tugamayapti
Sikl bor yoki to'xtash sharti yo'q. CYCLE yoki chuqurlik chegarasi qo'shing, psql'da Ctrl+C bilan bekor qiling.
8.4 Daraxt tartibsiz chiqdi
Rekursiv CTE natijasi tartibi kafolatlanmagan. Daraxt ko'rinishi uchun ORDER BY yol yoki SEARCH DEPTH FIRST.
9. Mashqlar
Mashq fayllari kurs/mashqlar/26/27-rekursiv/ da. Hammasi vazifalar holat 23 da.
1-mashq (oson): 1 dan 10 gacha
Rekursiv CTE bilan 1 dan 10 gacha sonlarni yasang va ularning yig'indisi hamda sonini chiqaring. Yordam: anchor SELECT 1 AS n, rekursiv qismda WHERE n < 10.
Yechim
WITH RECURSIVE sonlar AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM sonlar WHERE n < 10
)
SELECT sum(n) AS yigindi, count(*) AS soni FROM sonlar; yigindi | soni
---------+------
55 | 10
(1 row)To'xtash sharti WHERE n < 10: n = 10 bo'lgan qatordan keyin yangi qator yaratilmaydi. 1 + 2 + … + 10 = 55.
2-mashq (o'rta): Azizning portfolio ishi
Azizning "Portfolio saytiga blog yozish #portfolio" vazifasini (id 17) va uning hamma kichik vazifalarini chuqurlik bilan chiqaring: id, matn, chuqurlik; chuqurlik, keyin id bo'yicha. Yordam: «Ikki qism» bo'limidagi so'rov, anchor'da id = 17.
Yechim
WITH RECURSIVE daraxt AS (
SELECT id, matn, 1 AS chuqurlik FROM vazifalar WHERE id = 17
UNION ALL
SELECT v.id, v.matn, d.chuqurlik + 1
FROM vazifalar v JOIN daraxt d ON v.ota_id = d.id
)
SELECT id, matn, chuqurlik FROM daraxt ORDER BY chuqurlik, id; id | matn | chuqurlik
----+------------------------------------------+-----------
17 | Portfolio saytiga blog yozish #portfolio | 1
43 | Blog uchun mavzu tanlash | 2
44 | Qoralama yozish | 2
45 | Rasmlarni siqish | 2
(4 rows)Bu daraxt bir darajali (3 ta kichik vazifa, ularning farzandi yo'q). Rekursiya 3-qadamda bo'sh ishchi jadval bilan to'xtadi.
3-mashq (qiyin): barglardan ildizgacha yo'l
Har barg (kichik vazifa, o'zining farzandi yo'q) uchun ildizdan shu bargacha id'lar yo'lini 7/41/46 ko'rinishida chiqaring: barg, yol. Yordam: anchor — barglar (ota_id IS NOT NULL va NOT EXISTS farzand), rekursiv qism yuqoriga yuradi va yo'l boshiga ota id sini qo'shadi (o.id || '/' || y.yol); ildizga yetganda (ota_id IS NULL) qatorni oling.
Yechim
WITH RECURSIVE yuqoriga AS (
SELECT k.id AS barg, k.ota_id, k.id::text AS yol
FROM vazifalar k
WHERE k.ota_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM vazifalar b WHERE b.ota_id = k.id)
UNION ALL
SELECT y.barg, o.ota_id, o.id || '/' || y.yol
FROM yuqoriga y JOIN vazifalar o ON o.id = y.ota_id
)
SELECT barg, yol FROM yuqoriga
WHERE ota_id IS NULL ORDER BY barg; barg | yol
------+---------
42 | 7/42
43 | 17/43
44 | 17/44
45 | 17/45
46 | 7/41/46
47 | 7/41/47
(6 rows)Har qadamda ota_id bir daraja yuqoriga suriladi. Ildizga yetganda ota_id NULL bo'ladi — oxirgi WHERE faqat shu "to'liq" yo'llarni oladi, oraliq qadamlarni tashlaydi. k.id::text anchor'da turini text qiladi — «Turlar mos emas» xatosiga yo'l qo'ymaydi. Bunday yo'l satri materialized path naqshining asosi (Modellash naqshlari).
4-mashq: SQL qadami — o'chirishdan oldin ogohlantirish
25-qism API'sida vazifa o'chirilganda kichik vazifalar ham o'chadi (vazifalar_ota_id_fkey ... ON DELETE CASCADE). Ilova foydalanuvchiga oldindan aytishi kerak: "Bu vazifa bilan 4 ta kichik vazifa ham o'chadi". 7-vazifa uchun shu sonni va o'chadigan id'larni chiqaring. Sxema o'zgarmaydi — commit yo'q. Yordam: rekursiv CTE, keyin count(*) - 1 va array_agg(id ORDER BY id).
Yechim
WITH RECURSIVE daraxt AS (
SELECT id FROM vazifalar WHERE id = 7
UNION ALL
SELECT v.id FROM vazifalar v JOIN daraxt d ON v.ota_id = d.id
)
SELECT count(*) - 1 AS kichiklari,
array_agg(id ORDER BY id) AS idlar
FROM daraxt; kichiklari | idlar
------------+-----------------
4 | {7,41,42,46,47}
(1 row)ON DELETE CASCADE ham ichkarida xuddi shu daraxtni yuradi: 7 o'chsa — 41, 42; 41 o'chsa — 46, 47. Endi siz buni o'chirishdan oldin ko'rsata olasiz.
10. Real ishda
- Daraxtlar hamma joyda: do'kon kategoriyalari (Elektronika › Telefonlar › Smartfonlar), izohlarga javoblar, kompaniya tuzilmasi (kim kimga bo'ysunadi), fayl papkalari, menyu.
- Graflar: "do'stlarning do'stlari", marshrutlar, bog'liqliklar (qaysi paket qaysiga bog'liq) —
CYCLEbilan. - Muqobil modellar: juda chuqur va ko'p o'qiladigan daraxt uchun materialized path (
ltreekengaytmasi) yoki closure table — Modellash naqshlari darsida. Bizningota_id(adjacency list) — eng sodda va yozish uchun eng qulay. - ORM'lar (masalan Prisma) rekursiv CTE'ni to'g'ridan qo'llab-quvvatlamaydi — bunday so'rov xom SQL bilan yoziladi (27-qism). Kysely kabi query builder'larda esa
withRecursivebor. - Intervyu: "xodimlar jadvalida har xodimning hamma boshliqlarini toping" — klassik rekursiv CTE masalasi.
Xulosa
WITH RECURSIVE: anchor (boshlanish) +UNION ALL+ rekursiv qism (o'ziga JOIN).- Postgres ishchi jadval bilan sikl qiladi: har qadam faqat oldingi qadam topganlarini ko'radi, bo'sh qolsa — to'xtaydi.
- Chuqurlik —
n + 1, yo'l —ARRAY[id]vayol || id; daraxt tartibi —ORDER BY yolyokiSEARCH DEPTH FIRST. - Pastga yurish —
v.ota_id = d.id, yuqoriga —v.id = y.ota_id. - Siklli ma'lumotda —
CYCLE(PG 14+) yoki chuqurlik chegarasi; anchor va rekursiv qism turlari bir xil bo'lsin.
Keyingi dars: Window funksiyalar 1: ranking — qatorlarni guruhlamasdan, har biriga guruh ichidagi o'rnini beramiz.
Manbalar
- PostgreSQL 18 hujjati — WITH Queries, Recursive Queries, Search Order, Cycle Detection: https://www.postgresql.org/docs/18/queries-with.html#QUERIES-WITH-RECURSIVE
- PostgreSQL 14 reliz eslatmalari (SEARCH va CYCLE bandlari): https://www.postgresql.org/docs/14/release-14.html
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!