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.

🕑 9 daqiqa o‘qish 📄 539 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. WITH - so'rovni bo'laklash
  2. WITH RECURSIVE - ierarxiya
  3. Yo'lni saqlash
  4. Sanoq qatori yasash
  5. Xulosa

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.

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

SQL
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;
Natija
   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:

SQL
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;
Natija
   ism   |     lavozim      |    maosh
---------+------------------+-------------
 Malika  | bo'lim boshlig'i | 12000000.00
 Nodira  | bo'lim boshlig'i | 11500000.00
 Dilnoza | mutaxassis       |  7200000.00
(3 rows)
CTE va ichki so'rov - qaysi biri tezroq?

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:

YozuvMa'nosi
AS MATERIALIZEDMajburan alohida hisobla va saqla
AS NOT MATERIALIZEDMajburan 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.

Rekursiv so'rovning ikki qismi 1. Boshlang'ich qism Daraxtning ildizi - bir marta bajariladi UNION ALL 2. Rekursiv qism O'zining OLDINGI natijasiga murojaat qiladi - takrorlanadi Har takrorlanishda yangi qator topilmaguncha davom etadi 1-qadam: Husanboy 2-qadam: Malika, Nodira 3-qadam: Aziza, Kamola... Eng katta xavf - cheksiz aylanish Ma'lumotda halqa bo'lsa (A ning rahbari B, B ning rahbari A), so'rov to'xtamaydi Himoya: chuqurlikni sanab, LIMIT yoki daraja sharti qo'yish
Rekursiv qism o'z natijasiga qayta murojaat qiladi - shuning uchun "rekursiv"
SQL
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;
Natija
 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:

SQL
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;
Natija
   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)
Halqa bo'lsa so'rov to'xtamaydi

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:

UsulYozuv
Chuqurlikni cheklashWHERE d.daraja < 10
Yo'lni tekshirishWHERE 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):

SQL
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;
Natija
   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:

SQL
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;
Natija
 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).

Amaliy topshiriq
  1. WITH bilan lavozim bo'yicha o'rtacha maoshni hisoblang.
  2. Undan yuqori maosh oladiganlarni chiqaring.
  3. Ikkita CTE yozing, ikkinchisi birinchisidan foydalansin.
  4. MATERIALIZED qachon foydali ekanini yozing.
  5. WITH RECURSIVE bilan butun ierarxiyani chiqaring.
  6. Boshlang'ich qismni Malikadan boshlang - natija qanday o'zgardi?
  7. daraja ustuni qanday hisoblanishini tushuntiring.
  8. Yo'l zanjirini > o'rniga / bilan yozing.
  9. Halqadan himoya qiluvchi variantni sinang.
  10. Rekursiya bilan 1 dan 10 gacha sonlarni chiqaring.

Xulosa #

  • WITH so'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.
  • MATERIALIZED majburan alohida hisoblashni, NOT MATERIALIZED esa teskarisini so'raydi.
  • WITH RECURSIVE ikki qismdan iborat: boshlang'ich va rekursiv, orasida UNION 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 id larni massivda saqlash.
  • Oddiy ketma-ketlik uchun rekursiya shart emas - generate_series() bor.

Keyingi bo'limda PostgreSQL ning massiv turini ko'ramiz.

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.