10-bo‘lim
CTE va rekursiv so'rovlar
WITH bilan so'rovni bo'laklash, CTE va ichki so'rov farqi, WITH RECURSIVE bilan ierarxiya va MATERIALIZED boshqaruvi.
Ushbu bo‘lim mundarijasi
Murakkab so'rov bir necha bosqichdan iborat bo'ladi. Ularni ichma-ich yozish o'qishni qiyinlashtiradi.
WITH yozuvi - umumiy jadval ifodasi (CTE) - so'rovni
nomlangan bosqichlarga ajratadi.
CREATE TABLE xodim (
id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
ism text NOT NULL,
lavozim text NOT NULL,
rahbar_id int REFERENCES xodim(id),
maosh numeric(10,2) NOT NULL
);
INSERT INTO xodim (ism, lavozim, rahbar_id, maosh) VALUES
('Husanboy', 'direktor', NULL, 18000000),
('Malika', 'bo''lim boshlig''i', 1, 12000000),
('Nodira', 'bo''lim boshlig''i', 1, 11500000),
('Aziza', 'mutaxassis', 2, 7000000),
('Kamola', 'mutaxassis', 2, 6800000),
('Dilnoza', 'mutaxassis', 3, 7200000),
('Bekzod', 'stajyor', 4, 3000000);
WITH - so'rovni bo'laklash #
Ichma-ich yozilgan so'rovni o'qish qiyin. WITH uni tepadan
pastga o'qiladigan qiladi:
WITH bolim_ortacha AS (
SELECT lavozim, avg(maosh) AS ortacha
FROM xodim
GROUP BY lavozim
)
SELECT x.ism, x.lavozim, x.maosh, round(b.ortacha, 0) AS lavozim_ortacha
FROM xodim x
JOIN bolim_ortacha b ON b.lavozim = x.lavozim
WHERE x.maosh > b.ortacha
ORDER BY x.maosh DESC;
ism | lavozim | maosh | lavozim_ortacha
---------+------------------+-------------+-----------------
Malika | bo'lim boshlig'i | 12000000.00 | 11750000
Dilnoza | mutaxassis | 7200000.00 | 7000000
(2 rows)
Bir nechta CTE ni ketma-ket yozish mumkin - har biri oldingisiga murojaat qila oladi:
WITH bosh AS (
SELECT * FROM xodim WHERE rahbar_id IS NOT NULL
),
yuqori_maosh AS (
SELECT * FROM bosh WHERE maosh > 7000000
)
SELECT ism, lavozim, maosh FROM yuqori_maosh ORDER BY maosh DESC;
ism | lavozim | maosh
---------+------------------+-------------
Malika | bo'lim boshlig'i | 12000000.00
Nodira | bo'lim boshlig'i | 11500000.00
Dilnoza | mutaxassis | 7200000.00
(3 rows)
Eski PostgreSQL da (12-versiyagacha) WITH har doim
alohida bajarilar va natijasi vaqtincha saqlanardi. Bu
ba'zan sekinlashtirardi.
12-versiyadan boshlab PostgreSQL o'zi qaror qiladi: agar CTE bir marta ishlatilsa, uni asosiy so'rovga qo'shib yuboradi.
Kerak bo'lsa, qarorni o'zingiz aytishingiz mumkin:
| Yozuv | Ma'nosi |
|---|---|
AS MATERIALIZED | Majburan alohida hisobla va saqla |
AS NOT MATERIALIZED | Majburan asosiy so'rovga qo'sh |
MATERIALIZED og'ir CTE bir necha marta ishlatilganda
foydali - u ikki marta hisoblanmaydi.
WITH RECURSIVE - ierarxiya #
Xodimlar jadvalida rahbar-xodim zanjiri bor. "Husanboyning butun bo'ysunuvchilar daraxti" ni qanday olish mumkin?
Oddiy JOIN bilan bo'lmaydi - chuqurlik oldindan noma'lum.
WITH RECURSIVE aynan shu uchun.
WITH RECURSIVE daraxt AS (
SELECT id, ism, lavozim, rahbar_id, 1 AS daraja
FROM xodim
WHERE rahbar_id IS NULL
UNION ALL
SELECT x.id, x.ism, x.lavozim, x.rahbar_id, d.daraja + 1
FROM xodim x
JOIN daraxt d ON x.rahbar_id = d.id
)
SELECT daraja, repeat(' ', daraja - 1) || ism AS ierarxiya, lavozim
FROM daraxt
ORDER BY daraja, ism;
daraja | ierarxiya | lavozim
--------+--------------+------------------
1 | Husanboy | direktor
2 | Malika | bo'lim boshlig'i
2 | Nodira | bo'lim boshlig'i
3 | Aziza | mutaxassis
3 | Dilnoza | mutaxassis
3 | Kamola | mutaxassis
4 | Bekzod | stajyor
(7 rows)
repeat(' ', daraja - 1) || ism - bu shunchaki chiroyli
ko'rsatish uchun: har daraja ikki bo'sh joy bilan suriladi.
Daraja ustuni rekursiyaning o'zida hisoblandi: boshlang'ich
qismda 1, har takrorlanishda +1.
Yo'lni saqlash #
Ierarxiyada "kimdan kimgacha" yo'lni ham yig'ish mumkin:
WITH RECURSIVE yol AS (
SELECT id, ism, rahbar_id, ism AS zanjir
FROM xodim WHERE rahbar_id IS NULL
UNION ALL
SELECT x.id, x.ism, x.rahbar_id, y.zanjir || ' > ' || x.ism
FROM xodim x JOIN yol y ON x.rahbar_id = y.id
)
SELECT ism, zanjir FROM yol ORDER BY zanjir;
ism | zanjir
----------+------------------------------------
Husanboy | Husanboy
Malika | Husanboy > Malika
Aziza | Husanboy > Malika > Aziza
Bekzod | Husanboy > Malika > Aziza > Bekzod
Kamola | Husanboy > Malika > Kamola
Nodira | Husanboy > Nodira
Dilnoza | Husanboy > Nodira > Dilnoza
(7 rows)
Ma'lumotda xato bo'lsa - masalan A ning rahbari B, B ning rahbari esa A - rekursiya cheksiz aylanadi.
PostgreSQL buni o'zi aniqlamaydi. So'rov xotira tugaguncha ishlaydi.
Ikki himoya usuli bor:
| Usul | Yozuv |
|---|---|
| Chuqurlikni cheklash | WHERE d.daraja < 10 |
| Yo'lni tekshirish | WHERE NOT x.id = ANY(y.yol_massiv) |
Ikkinchisi ishonchliroq: u haqiqiy halqani topadi, chuqurlikni esa cheklamaydi.
Ishlab chiqarish so'rovlarida har doim shulardan birini qo'ying.
Halqadan himoyalangan variant massiv bilan yoziladi (11-bo'limda massivlarni batafsil ko'ramiz):
WITH RECURSIVE xavfsiz AS (
SELECT id, ism, rahbar_id, ARRAY[id] AS korilgan
FROM xodim WHERE rahbar_id IS NULL
UNION ALL
SELECT x.id, x.ism, x.rahbar_id, s.korilgan || x.id
FROM xodim x
JOIN xavfsiz s ON x.rahbar_id = s.id
WHERE NOT x.id = ANY(s.korilgan)
)
SELECT ism, korilgan FROM xavfsiz ORDER BY id;
ism | korilgan
----------+-----------
Husanboy | {1}
Malika | {1,2}
Nodira | {1,3}
Aziza | {1,2,4}
Kamola | {1,2,5}
Dilnoza | {1,3,6}
Bekzod | {1,2,4,7}
(7 rows)
korilgan massivi allaqachon ko'rilgan id larni saqlaydi.
Agar qator qaytadan uchrasa, u tashlab yuboriladi.
Sanoq qatori yasash #
Rekursiya faqat jadval uchun emas - u ketma-ketlik yaratishga ham yaraydi:
WITH RECURSIVE sanoq AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM sanoq WHERE n < 5
)
SELECT n, n * n AS kvadrat FROM sanoq;
n | kvadrat
---+---------
1 | 1
2 | 4
3 | 9
4 | 16
5 | 25
(5 rows)
Amalda buning uchun tayyor funksiya ham bor:
generate_series(1, 5).
WITHbilan lavozim bo'yicha o'rtacha maoshni hisoblang.- Undan yuqori maosh oladiganlarni chiqaring.
- Ikkita CTE yozing, ikkinchisi birinchisidan foydalansin.
MATERIALIZEDqachon foydali ekanini yozing.WITH RECURSIVEbilan butun ierarxiyani chiqaring.- Boshlang'ich qismni Malikadan boshlang - natija qanday o'zgardi?
darajaustuni qanday hisoblanishini tushuntiring.- Yo'l zanjirini
>o'rniga/bilan yozing. - Halqadan himoya qiluvchi variantni sinang.
- Rekursiya bilan 1 dan 10 gacha sonlarni chiqaring.
Xulosa #
WITHso'rovni nomlangan bosqichlarga ajratadi - ichma-ich yozishdan o'qiluvchanroq.- Bir nechta CTE ketma-ket yozilishi va bir-biriga murojaat qilishi mumkin.
- PostgreSQL 12 dan boshlab CTE odatda asosiy so'rovga qo'shib yuboriladi.
MATERIALIZEDmajburan alohida hisoblashni,NOT MATERIALIZEDesa teskarisini so'raydi.WITH RECURSIVEikki qismdan iborat: boshlang'ich va rekursiv, orasidaUNION ALL.- Rekursiv qism CTE ning o'ziga murojaat qiladi.
- Daraja va yo'l kabi qiymatlarni rekursiya davomida yig'ib borish mumkin.
- Ma'lumotda halqa bo'lsa, so'rov to'xtamaydi - PostgreSQL buni o'zi aniqlamaydi.
- Himoya: chuqurlikni cheklash yoki ko'rilgan
idlarni massivda saqlash. - Oddiy ketma-ketlik uchun rekursiya shart emas -
generate_series()bor.
Keyingi bo'limda PostgreSQL ning massiv turini ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.