IlmHamroh
JavaScript Full-stack/26-qism. SQL va PostgreSQL25/64-dars18 daqiqa
Mundarija (33)

SQL subquery va LATERAL: IN, EXISTS, correlated va "har guruhdan oxirgi N ta"

Qisqacha: Subquery (ichki so'rov) — qavs ichidagi boshqa SELECT: u bitta qiymat (scalar), qiymatlar ro'yxati (IN), "bormi?" javobi (EXISTS) yoki butun jadval (FROM (...)) beradi. Correlated ichki so'rov tashqi qatorga tayanadi va go'yo har qator uchun qayta bajariladi. LATERAL esa FROM dagi ichki so'rovga chapdagi jadvalni ko'rsatadi. "Har foydalanuvchining oxirgi 2 vazifasi" kabi masala shu bilan bitta so'rovda yechiladi.

Bu darsda

  • Scalar ichki so'rov yozasiz va u bir nechta qator qaytarganda nima bo'lishini bilasiz.
  • IN va EXISTS ni ishlatasiz, farqini va NOT IN tuzog'ini tushuntirasiz.
  • Correlated ichki so'rov va anti-join (jufti yo'qlarni topish) yozasiz.
  • FROM ichida so'rov va LATERAL bilan "har guruhdan N ta" masalasini yechasiz.
  • 25-qism API'si uchun teg bo'yicha filtr so'rovini yozasiz.

Oldin bilishingiz kerak: JOIN'lar, NULL mantiqi, Agregatlar, GROUP BY va HAVING.

1. Nega bu kerak?

JOIN'lar darsida jadvallarni birlashtirdik. Lekin ba'zi savollarga JOIN bilan javob berish noqulay yoki imkonsiz:

  • "Har foydalanuvchining oxirgi 2 vazifasi" — JOIN hammasini olib keladi, LIMIT 2 esa butun natijani kesadi, har foydalanuvchini emas.
  • "O'zining o'rtacha bajarish vaqtidan sekin bajarilgan vazifalar" — avval har odam uchun o'rtachani hisoblash, keyin u bilan solishtirish kerak.
  • "#oila tegi bor foydalanuvchilar" — teg tafsiloti kerak emas, faqat "bormi?" javobi.

Bularning hammasi — so'rov ichida yana bir so'rov. O'xshatish: do'konda kassir "bu non qancha?" deb narxlar ro'yxatiga qaraydi, keyin hisobni davom ettiradi. Narxga qarash — kichik ichki savol, asosiy ish — hisob. Ichki so'rov ham shunday: asosiy so'rov o'z ishini qilayotib, kichik savolga javob oladi.

2. Scalar ichki so'rov

2.1 Bitta qiymat qaytaradigan so'rov

Scalar subquery (skalyar ichki so'rov) — aynan bitta qator, bitta ustun qaytaradi va oddiy qiymat o'rnida ishlatiladi. Har foydalanuvchining vazifalari soni, SELECT ro'yxati ichida:

sql
SELECT ism,
  (SELECT count(*) FROM vazifalar v
   WHERE v.egasi_id = f.id) AS vazifalar
FROM foydalanuvchilar f ORDER BY id;
text
        ism         | vazifalar
--------------------+-----------
 Aziz Karimov       |        12
 Dilnoza Rahimova   |         6
 Sardor Tursunov    |         6
 Madina Yusupova    |         9
 Javohir Olimov     |         6
 Gulnora Saidova    |         4
 Bekzod Nazarov     |         4
 Shahnoza Ergasheva |         0
(8 rows)

Bu kanon tajribasi (PostgreSQL 18.6, vazifalar holat 23). Natija JOIN'lar darsidagi LEFT JOIN + count(v.id) bilan bir xil. Ichki so'rov f.id ni — tashqi so'rovning qatorini — ishlatyapti. Bunday ichki so'rov correlated (bog'liq) deyiladi, u haqda alohida bo'limda gaplashamiz.

Ichki so'rov WHERE da ham ishlaydi. Oxirgi vazifadan 7 kun ichida yaratilganlar (biz ishga tushirdik, PostgreSQL 17.6):

sql
SELECT count(*) AS soni FROM vazifalar
WHERE yaratilgan_vaqt > (
  SELECT max(yaratilgan_vaqt) - interval '7 days' FROM vazifalar
);
text
 soni
------
   11
(1 row)

Bu ichki so'rov tashqiga bog'liq emas — u bir marta hisoblanadi va natijasi oddiy son kabi ishlatiladi.

2.2 Bir nechta qator — xato, nol qator — NULL

Scalar ichki so'rov ikki qator qaytarsa:

sql
SELECT (SELECT id FROM vazifalar WHERE egasi_id = 1);
text
ERROR:  more than one row returned by a subquery used as an expression

Tarjimasi: "ifoda sifatida ishlatilgan ichki so'rov bir nechta qator qaytardi". Postgres 12 ta id dan qaysi birini olishni bilmaydi. Xato bajarish vaqtida chiqadi: bugun bitta qator bo'lib ishlagan so'rov ertaga ikkinchi qator qo'shilganda yiqiladi. Shuning uchun scalar ichki so'rovda bitta qatorni kafolatlang: count, max kabi agregat, LIMIT 1 yoki noyob kalit bo'yicha shart.

Nol qator esa xato emas — natija NULL:

sql
SELECT ism,
  (SELECT matn FROM vazifalar v
   WHERE v.egasi_id = f.id AND v.ustuvorlik = 'past') AS past
FROM foydalanuvchilar f WHERE f.id IN (3, 7, 8) ORDER BY f.id;
text
        ism         | past
--------------------+------
 Sardor Tursunov    |
 Bekzod Nazarov     |
 Shahnoza Ergasheva |
(3 rows)

Uchalasida past ustuvorlikdagi vazifa yo'q — bo'sh katak NULL (NULL mantiqi).

Tekshirib ko'ring: (SELECT matn FROM vazifalar WHERE egasi_id = 1 ORDER BY yaratilgan_vaqt DESC LIMIT 1) scalar ichki so'rov sifatida xavfsizmi?

Javob

Ha. LIMIT 1 ko'pi bilan bitta qatorni kafolatlaydi — "more than one row" xatosi chiqmaydi. Vazifa bo'lmasa NULL keladi. ORDER BY muhim: usiz "qaysi biri bitta" — tasodifiy.

3. IN va EXISTS

3.1 Ro'yxatda bormi?

#oila tegi bor foydalanuvchilar — ikki usulda:

sql
SELECT ism FROM foydalanuvchilar
WHERE id IN (SELECT t.egasi_id FROM teglar t WHERE t.nom = '#oila')
ORDER BY ism;
text
       ism
------------------
 Aziz Karimov
 Dilnoza Rahimova
 Madina Yusupova
(3 rows)
sql
SELECT ism FROM foydalanuvchilar f
WHERE EXISTS (
  SELECT 1 FROM teglar t WHERE t.egasi_id = f.id AND t.nom = '#oila'
)
ORDER BY ism;
text
       ism
------------------
 Aziz Karimov
 Dilnoza Rahimova
 Madina Yusupova
(3 rows)
Usul Qanday o'qiladi
id IN (SELECT ...) id shu ro'yxatda bormi?
EXISTS (SELECT ...) shunday qator kamida bitta bormi?

EXISTS faqat "bor yoki yo'q" ni tekshiradi — ichkaridagi ustunlar ahamiyatsiz. Shuning uchun SELECT 1 yoziladi. Birinchi mos qator topilishi bilan qidiruv to'xtaydi.

JOIN'dan farqi: #oila bir odamda ikki vazifada bo'lsa ham, IN/EXISTS odamni bir marta qaytaradi. JOIN esa ikki marta qaytarardi (JOIN'lar darsidagi fan-out).

3.2 Qaysi biri tezroq?

Ko'p qo'llanmalarda "EXISTS IN dan tez" deyiladi. Postgres'da bu odatda to'g'ri emas: planner (Postgres'ning so'rovni qanday bajarishni hal qiladigan qismi) ikkalasini ham bir xil rejaga (plan) aylantiradi. Biz tekshirdik (PostgreSQL 17.6, EXPLAIN — so'rov rejasini ko'rsatadi). Chiqishda faqat birinchi qatorga qarang — bajarish usuli Hash Join:

sql
EXPLAIN (COSTS OFF)
SELECT ism FROM foydalanuvchilar
WHERE id IN (SELECT t.egasi_id FROM teglar t WHERE t.nom = '#oila');
text
                   QUERY PLAN
-------------------------------------------------
 Hash Join
   Hash Cond: (foydalanuvchilar.id = t.egasi_id)
   ->  Seq Scan on foydalanuvchilar
   ->  Hash
         ->  Seq Scan on teglar t
               Filter: (nom = '#oila'::text)
(6 rows)

EXISTS varianti uchun ham aynan shu Hash Join rejasi chiqdi. Rejani o'qishni EXPLAIN ANALYZE'ni o'qish darsida o'rganamiz. Hozirgi xulosa: tanlovni tezlik emas, ma'no va NULL xulq-atvori belgilasin — keyingi bo'lim.

3.3 NOT IN va NULL tuzog'i

NULL mantiqi darsida ko'rgan edingiz: ro'yxatda bitta NULL bo'lsa, NOT IN hech qachon rost bo'lmaydi va 0 qator qaytaradi. Bizda vazifalar.egasi_id NOT NULL, shuning uchun bu ishlaydi:

sql
SELECT ism FROM foydalanuvchilar
WHERE id NOT IN (SELECT egasi_id FROM vazifalar);
text
        ism
--------------------
 Shahnoza Ergasheva
(1 row)

Lekin ichki so'rov NULL bo'lishi mumkin ustunni (masalan ota_id) qaytarsa, natija jimgina bo'sh bo'ladi. NOT EXISTS da bunday tuzoq yo'q. Qoida: "yo'q"ni tekshirishda NOT EXISTS ishlating.

Tekshirib ko'ring: WHERE id NOT IN (SELECT ota_id FROM vazifalar) — "hech kimning otasi bo'lmagan vazifalar" ni topadimi?

Javob

Yo'q — 0 qator qaytaradi. Ko'p vazifada ota_id NULL, ro'yxatga NULL tushadi va id NOT IN (..., NULL) har qatorda NULL (rost emas) bo'ladi. To'g'risi: WHERE NOT EXISTS (SELECT 1 FROM vazifalar k WHERE k.ota_id = v.id) yoki ichki so'rovga WHERE ota_id IS NOT NULL qo'shish.

4. Correlated ichki so'rov

4.1 Har qatorning o'z o'rtachasi

Vazifa: har foydalanuvchida o'zining o'rtacha bajarish vaqtidan sekin bajarilgan vazifalar. "O'rtacha" har odam uchun boshqa — ichki so'rov tashqi qatorning egasini bilishi kerak:

sql
SELECT v.egasi_id, v.matn,
       v.bajarilgan_vaqt - v.yaratilgan_vaqt AS vaqt
FROM vazifalar v
WHERE v.bajarildi
  AND v.bajarilgan_vaqt - v.yaratilgan_vaqt > (
    SELECT avg(x.bajarilgan_vaqt - x.yaratilgan_vaqt)
    FROM vazifalar x
    WHERE x.egasi_id = v.egasi_id AND x.bajarildi
  )
ORDER BY 1, 3 DESC;
text
 egasi_id |                     matn                      |      vaqt
----------+-----------------------------------------------+-----------------
        1 | Blog uchun mavzu tanlash                      | 22:55:00
        2 | Gaz to'lovi #to'lov                           | 2 days
        2 | Ingliz tili: 20 ta yangi so'z #o'qish #ingliz | 1 day 02:00:00
        3 | Moy almashtirish #mashina                     | 4 days 01:00:00
        4 | Adabiyotlar ro'yxati                          | 5 days 02:54:00
        4 | Yotoqxona to'lovi #to'lov                     | 3 days 04:00:00
        5 | Mijozga taklifnoma yuborish #ish              | 02:00:00
        6 | Un va yog' xarid qilish #bozor #oshxona       | 05:00:00
        7 | Algoritmlar: 5 ta masala #o'qish              | 2 days 03:00:00
(9 rows)

Bir jadval ikki rolda: v — tekshirilayotgan vazifa, x — o'rtacha uchun "hamkasblar". x.egasi_id = v.egasi_id — bog'liqlik nuqtasi. Vaqt ayirmasi interval turida (Ifodalar va funksiyalar), avg intervallarning o'rtachasini hisoblay oladi.

flowchart LR
  T["tashqi qator v<br/>egasi_id = 2"] --> I["ichki so'rov:<br/>2 ning o'rtachasi"]
  I --> C{"v ning vaqti<br/>kattami?"}
  C -->|ha| R["natijaga"]
  C -->|yo'q| S["tashlanadi"]

Diagrammadagi aylana har tashqi qator uchun takrorlanadi — mantiqan ichki so'rov qator soni marta bajariladi. Postgres ba'zan buni optimallashtiradi, ba'zan yo'q. 47 qatorda farq sezilmaydi, millionlab qatorda esa correlated ichki so'rov sekinlashishi mumkin. Unda o'rtachalarni bir marta hisoblab, JOIN qilish yaxshiroq — keyingi bo'limda.

4.2 Anti-join: tegi yo'q vazifalar

Anti-join — "jufti yo'qlarni top". NOT EXISTS bilan:

sql
SELECT v.id, v.matn FROM vazifalar v
WHERE NOT EXISTS (
  SELECT 1 FROM vazifa_teglari vt WHERE vt.vazifa_id = v.id
)
ORDER BY v.id;
text
 id |            matn
----+-----------------------------
 38 | Velosiped zanjirini moylash
 41 | Jadvallarni loyihalash
 42 | Adabiyotlar ro'yxati
 43 | Blog uchun mavzu tanlash
 44 | Qoralama yozish
 45 | Rasmlarni siqish
 46 | ER diagramma chizish
 47 | Normalizatsiyani tekshirish
(8 rows)

8 ta — JOIN'lar darsidagi FULL JOIN hisobidagi "tegsiz vazifa" soni bilan bir xil. Kichik vazifalar matnida # yo'q, shuning uchun ularning hammasi shu ro'yxatda. Uchta yo'l bilan bir xil natija olish mumkin:

Yo'l Izoh
NOT EXISTS (...) aniq ma'no, NULL tuzog'i yo'q — tavsiya
LEFT JOIN ... WHERE o'ng.id IS NULL JOIN uslubi, natija bir xil
NOT IN (...) ichida NULL bo'lsa — 0 qator

5. FROM ichida ichki so'rov

5.1 Hosila jadval

Ichki so'rov FROM da butun jadval vazifasini bajaradi — hosila jadval (derived table). Masalan "bir odamga o'rtacha nechta vazifa to'g'ri keladi?" — avval har odamning sonini, keyin ularning o'rtachasini olamiz (biz ishga tushirdik, PostgreSQL 17.6):

sql
SELECT round(avg(soni), 2) AS ortacha
FROM (SELECT egasi_id, count(*) AS soni
      FROM vazifalar GROUP BY egasi_id);
text
 ortacha
---------
    6.71
(1 row)

47 vazifa / 7 kishi = 6.71 (Shahnoza kirmadi — unda vazifa yo'q). Agregatni agregat ustidan to'g'ridan olib bo'lmaydi, shuning uchun ikki bosqich kerak.

PostgreSQL 16 dan oldin FROM dagi ichki so'rovga taxallus majburiy edi: FROM (...) AS s. 16-versiyadan beri ixtiyoriy. Eski versiyalar va boshqa bazalar uchun taxallus yozib qo'yish yaxshi odat.

5.2 Hosila jadval bilan JOIN

«Correlated ichki so'rov» bo'limidagi masalani boshqacha yozamiz: avval har odamning o'rtachasi — bir marta, keyin JOIN:

sql
SELECT v.egasi_id, v.matn
FROM vazifalar v
JOIN (SELECT egasi_id,
             avg(bajarilgan_vaqt - yaratilgan_vaqt) AS ortacha
      FROM vazifalar WHERE bajarildi
      GROUP BY egasi_id) o ON o.egasi_id = v.egasi_id
WHERE v.bajarildi
  AND v.bajarilgan_vaqt - v.yaratilgan_vaqt > o.ortacha
ORDER BY 1, 2;
text
 egasi_id |                     matn
----------+-----------------------------------------------
        1 | Blog uchun mavzu tanlash
        2 | Gaz to'lovi #to'lov
        2 | Ingliz tili: 20 ta yangi so'z #o'qish #ingliz
        3 | Moy almashtirish #mashina
        4 | Adabiyotlar ro'yxati
        4 | Yotoqxona to'lovi #to'lov
        5 | Mijozga taklifnoma yuborish #ish
        6 | Un va yog' xarid qilish #bozor #oshxona
        7 | Algoritmlar: 5 ta masala #o'qish
(9 rows)

O'sha 9 ta vazifa (biz ishga tushirdik, PostgreSQL 17.6). Endi o'rtachalar 7 marta emas, bitta guruhlash bilan hisoblanadi. Ichma-ich qavslar ko'paysa o'qish qiyinlashadi — keyingi darsda CTE bu bosqichlarga nom beradi.

Hosila jadvalsiz avg(count(*)) yozsak — ERROR: aggregate function calls cannot be nested ("agregat funksiya chaqiruvlari ichma-ich bo'lolmaydi").

6. LATERAL

6.1 "Har foydalanuvchining oxirgi 2 vazifasi"

Oddiy hosila jadval tashqi qatorni ko'rmaydi — u bir marta, mustaqil hisoblanadi. LATERAL kalit so'zi buni o'zgartiradi: FROM dagi ichki so'rov o'zidan chapdagi jadvallarning joriy qatorini ko'radi. Ya'ni u har chap qator uchun alohida bajariladi. Natijada har ism necha marta chiqqaniga qarang:

sql
SELECT f.ism, oxirgi.matn, oxirgi.yaratilgan_vaqt
FROM foydalanuvchilar f
CROSS JOIN LATERAL (
  SELECT v.matn, v.yaratilgan_vaqt FROM vazifalar v
  WHERE v.egasi_id = f.id
  ORDER BY v.yaratilgan_vaqt DESC LIMIT 2
) AS oxirgi
ORDER BY f.id, oxirgi.yaratilgan_vaqt DESC;
text
       ism        |                      matn                       |    yaratilgan_vaqt
------------------+-------------------------------------------------+------------------------
 Aziz Karimov     | Velosiped zanjirini moylash                     | 2026-10-18 12:20:00+00
 Aziz Karimov     | SQL JOIN mashqlarini yechish #o'qish            | 2026-10-14 14:30:00+00
 Dilnoza Rahimova | Rezyume yangilash #ish                          | 2026-10-16 17:00:00+00
 Dilnoza Rahimova | Gaz to'lovi #to'lov                             | 2026-10-11 04:30:00+00
 Sardor Tursunov  | Futbol: payshanba 20:00 #sport                  | 2026-10-15 14:00:00+00
 Sardor Tursunov  | Soliq hisobotini topshirish #ish #to'lov        | 2026-10-13 06:00:00+00
 Madina Yusupova  | Uyga qo'ng'iroq qilish #oila                    | 2026-10-17 16:10:00+00
 Madina Yusupova  | Python laboratoriya ishi #o'qish                | 2026-10-12 18:40:00+00
 Javohir Olimov   | Sport zalga yozilish #sport                     | 2026-10-19 02:30:00+00
 Javohir Olimov   | Yangi xodimga kirish huquqi berish #ish #server | 2026-10-09 09:00:00+00
 Gulnora Saidova  | Instagram uchun surat olish #ish                | 2026-10-12 10:30:00+00
 Gulnora Saidova  | Un va yog' xarid qilish #bozor #oshxona         | 2026-10-07 02:15:00+00
 Bekzod Nazarov   | Do'stlar bilan choyxona #dam                    | 2026-10-18 14:00:00+00
 Bekzod Nazarov   | Noutbukka SSD qo'yish #ta'mir                   | 2026-10-14 08:45:00+00
(14 rows)

7 kishi × 2 = 14 qator. LIMIT 2 endi har foydalanuvchi uchun ishlaydi — "Nega bu kerak?" bo'limidagi birinchi savolga javob. Tashqi ORDER BY natijani chiroyli tartiblaydi, ichki ORDER BY esa "qaysi 2 ta" ni tanlaydi.

O'xshatish: navbatdagi har mijoz uchun sotuvchi omborga alohida borib, aynan shu mijozga kerak ikki narsani olib chiqadi. LATERAL — "har chap qator uchun alohida bor".

6.2 LEFT JOIN LATERAL ... ON true

Shahnoza yuqorida yo'q — CROSS JOIN jufti yo'qni tashlaydi. Uni saqlash uchun LEFT JOIN LATERAL. Lekin LEFT JOIN ON talab qiladi, shart esa allaqachon ichki so'rovda — shuning uchun ON true ("har doim juft"):

sql
SELECT f.ism, o.matn FROM foydalanuvchilar f
LEFT JOIN LATERAL (
  SELECT matn FROM vazifalar v WHERE v.egasi_id = f.id
  ORDER BY yaratilgan_vaqt DESC LIMIT 1
) o ON true
ORDER BY f.id;
text
        ism         |               matn
--------------------+----------------------------------
 Aziz Karimov       | Velosiped zanjirini moylash
 Dilnoza Rahimova   | Rezyume yangilash #ish
 Sardor Tursunov    | Futbol: payshanba 20:00 #sport
 Madina Yusupova    | Uyga qo'ng'iroq qilish #oila
 Javohir Olimov     | Sport zalga yozilish #sport
 Gulnora Saidova    | Instagram uchun surat olish #ish
 Bekzod Nazarov     | Do'stlar bilan choyxona #dam
 Shahnoza Ergasheva |
(8 rows)

6.3 LATERAL'siz — xato

sql
SELECT f.ism, o.matn FROM foydalanuvchilar f,
  (SELECT matn FROM vazifalar v WHERE v.egasi_id = f.id LIMIT 1) o;
text
ERROR:  invalid reference to FROM-clause entry for table "f"
LINE 2: ... (SELECT matn FROM vazifalar v WHERE v.egasi_id = f.id LIMIT...
                                                             ^
DETAIL:  There is an entry for table "f", but it cannot be referenced from this part of the query.
HINT:  To reference that table, you must mark this subquery with LATERAL.

Tarjimasi: "f jadvaliga noto'g'ri havola. Tafsilot: f bor, lekin so'rovning bu qismidan unga murojaat qilib bo'lmaydi. Maslahat: buning uchun ichki so'rovni LATERAL deb belgilang". Postgres xabarining o'zi yechimni aytyapti.

UPSERT va MERGE darsida CROSS JOIN LATERAL teglarni_ajrat(v.matn) ni ko'rgan edingiz. Endi tushunarli: funksiya har vazifa uchun uning matni bilan chaqiriladi. FROM dagi funksiyalar uchun LATERAL so'zi ixtiyoriy — Postgres ularni o'zi lateral deb hisoblaydi. Ichki so'rov uchun esa majburiy.

Tekshirib ko'ring: "har foydalanuvchining oxirgi 2 vazifasi" ni LATERAL siz, oddiy JOIN ... ORDER BY ... LIMIT 14 bilan yozsa bo'ladimi?

Javob

Yo'q. LIMIT 14 butun natijadan eng yangi 14 tani oladi — faol odamning 5 vazifasi kirib, boshqasiniki kirmasligi mumkin. "Har guruhdan N ta" uchun ikki yo'l bor: LATERAL (shu dars) yoki window funksiya row_number() (Window funksiyalar 1).

7. Ko'p uchraydigan xatolar

7.1 more than one row returned by a subquery used as an expression

Scalar ichki so'rov bir nechta qator qaytardi. Agregat, LIMIT 1 yoki noyob shart qo'shing. Ehtimol sizga IN yoki EXISTS kerak edi.

7.2 NOT IN bo'sh natija berdi

Ichki so'rovda NULL bor. NOT EXISTS ga o'ting.

7.3 invalid reference to FROM-clause entry for table "f"

FROM dagi ichki so'rov tashqi jadvalni ishlatyapti — LATERAL qo'shing.

7.4 subquery has too many columns

id IN (SELECT id, matn FROM ...) — IN uchun ichki so'rov bitta ustun qaytarishi kerak (yoki chap tomonda ham shuncha ustun: (a, b) IN (SELECT x, y ...)).

7.5 Correlated ichki so'rov sekin

Katta jadvalda har qator uchun qayta hisoblash qimmat. Hosila jadval + JOIN yoki CTE bilan bir marta hisoblang, ichki so'rovdagi shart ustuniga indeks qo'ying.

8. Mashqlar

Mashq fayllari kurs/mashqlar/26/25-subquery/ da. Hammasi vazifalar holat 23 da.

1-mashq (oson): hosila jadval

Kamida 4 ta bajarilgan vazifasi bor foydalanuvchilarni chiqaring: ism, soni; soni bo'yicha kamayish, keyin ism. HAVING ishlatmang — FROM ichidagi so'rov bilan yozing. Yordam: «FROM ichida ichki so'rov» bo'limi.

Yechim
sql
SELECT f.ism, s.soni
FROM foydalanuvchilar f
JOIN (SELECT egasi_id, count(*) AS soni FROM vazifalar
      WHERE bajarildi GROUP BY egasi_id) s ON s.egasi_id = f.id
WHERE s.soni >= 4
ORDER BY s.soni DESC, f.ism;
text
       ism       | soni
-----------------+------
 Aziz Karimov    |    5
 Madina Yusupova |    4
(2 rows)

Hosila jadval s oddiy jadval kabi JOIN qilindi, uning soni ustuni esa tashqi WHERE da ishlatildi. HAVING count(*) >= 4 bilan ham yozish mumkin edi — ikkalasi to'g'ri.

2-mashq (o'rta): kichik vazifasi yo'q vazifalar

Madinaning (egasi_id = 4) yuqori darajadagi (ota_id IS NULL) vazifalaridan kichik vazifasi yo'qlarini chiqaring: id, matn. Yordam: anti-join — NOT EXISTS, ichki so'rovda k.ota_id = v.id.

Yechim
sql
SELECT v.id, v.matn FROM vazifalar v
WHERE v.egasi_id = 4 AND v.ota_id IS NULL
  AND NOT EXISTS (
    SELECT 1 FROM vazifalar k WHERE k.ota_id = v.id
  )
ORDER BY v.id;
text
 id |                matn
----+-------------------------------------
 13 | Kutubxonaga kitob qaytarish #o'qish
 18 | Yotoqxona to'lovi #to'lov
 31 | Python laboratoriya ishi #o'qish
 37 | Uyga qo'ng'iroq qilish #oila
(4 rows)

7-vazifa ("Kurs ishi") chiqmadi — uning kichik vazifalari bor. NOT IN (SELECT ota_id ...) bilan yozsangiz — 0 qator (ota_id da NULLlar bor).

3-mashq (qiyin): eng yaqin muddat

Har foydalanuvchi uchun eng yaqin muddatli bajarilmagan vazifani chiqaring: ism, matn, muddat. Muddatsiz vazifalar hisobga olinmasin. Hamma foydalanuvchi chiqsin (vazifasi yo'q bo'lsa — bo'sh). Yordam: LEFT JOIN LATERAL (... ORDER BY v.muddat LIMIT 1) ... ON true.

Yechim
sql
SELECT f.ism, oxirgi.matn, oxirgi.muddat
FROM foydalanuvchilar f
LEFT JOIN LATERAL (
  SELECT v.matn, v.muddat FROM vazifalar v
  WHERE v.egasi_id = f.id AND NOT v.bajarildi
    AND v.muddat IS NOT NULL
  ORDER BY v.muddat LIMIT 1
) AS oxirgi ON true
ORDER BY f.id;
text
        ism         |                      matn                      |         muddat
--------------------+------------------------------------------------+------------------------
 Aziz Karimov       | Oshxonadagi jo'mrakni almashtirish #ta'mir #uy | 2026-10-20 13:00:00+00
 Dilnoza Rahimova   | IELTS writing task 2 mashqi #ingliz            | 2026-10-24 15:00:00+00
 Sardor Tursunov    | Do'konga yangi narxlarni kiritish #ish         | 2026-10-09 04:00:00+00
 Madina Yusupova    | Python laboratoriya ishi #o'qish               | 2026-10-22 03:30:00+00
 Javohir Olimov     | Server zaxirasini tekshirish #ish #server      | 2026-10-03 13:00:00+00
 Gulnora Saidova    | Instagram uchun surat olish #ish               | 2026-10-30 07:00:00+00
 Bekzod Nazarov     | Universitet kontrakti #to'lov                  | 2026-11-01 13:00:00+00
 Shahnoza Ergasheva |                                                |
(8 rows)

Sardor va Javohirning muddati o'tib ketgan (oktyabr boshi). "Muddati o'tgan" belgisi uchun bu so'rov asos bo'ladi. muddat IS NOT NULL siz ORDER BY da NULLlar oxirida keladi, bu holda ham to'g'ri ishlardi; lekin shart niyatni aniq ko'rsatadi.

4-mashq: SQL qadami — teg bo'yicha filtr

25-qism API'sida GET /vazifalar?teg=%23o'qish kabi filtr bo'ladi. O'z bazangizda Azizning (egasi_id = 1) #o'qish tegli vazifalarini yangidan eskiga chiqaring. Vazifa takrorlanmasin (JOIN emas, EXISTS). Sxema o'zgarmaydi — commit yo'q. Yordam: «IN va EXISTS» bo'limi; ichki so'rovda vazifa_teglari va teglar JOIN qilinadi.

Yechim
sql
SELECT v.id, v.matn
FROM vazifalar v
WHERE v.egasi_id = 1
  AND EXISTS (
    SELECT 1 FROM vazifa_teglari vt
    JOIN teglar t ON t.id = vt.teg_id
    WHERE vt.vazifa_id = v.id AND t.nom = '#o''qish'
  )
ORDER BY v.yaratilgan_vaqt DESC, v.id DESC;
text
 id |                       matn
----+--------------------------------------------------
 34 | SQL JOIN mashqlarini yechish #o'qish
  2 | Postgres'ni Docker'da ko'tarish #o'qish #docker
  1 | JavaScript kursining 26-qismini boshlash #o'qish
(3 rows)

API'da teg nomi foydalanuvchidan keladi — u so'rov matniga qo'shilmaydi, parametr sifatida beriladi (Parametrli so'rovlar va SQL injection). Bir nechta teg bilan filtr (#o'qish VA #docker) — har teg uchun alohida EXISTS.

9. Real ishda

  • "Har guruhdan N ta" — oxirgi 3 buyurtma, har kategoriyadan eng arzon 5 mahsulot, har chatning oxirgi xabari (Telegram chat ro'yxati aynan shunday). Postgres'da LATERAL + indeks bu masalaning eng tez yechimlaridan biri.
  • EXISTS filtrlari — "teg bo'yicha", "ruxsati bor", "kamida bitta to'lovi bor". JOIN + DISTINCT o'rniga.
  • Anti-join — "hali to'lamaganlar", "hech qachon kirmagan foydalanuvchilar", "javobsiz qolgan murojaatlar".
  • ORM'larda ichki so'rov ko'pincha "N+1" muammosining yechimi: har qator uchun alohida so'rov o'rniga bitta so'rov (N+1 muammosi).
  • Intervyu: "IN va EXISTS farqi", "NOT IN nega xavfli", "correlated subquery nima" — tez-tez so'raladi.

Xulosa

  • Scalar ichki so'rov — bitta qiymat; ikki qator — xato, nol qator — NULL.
  • IN — ro'yxatda bormi, EXISTS — kamida bitta bormi; Postgres ko'pincha ikkalasini bir xil rejaga aylantiradi.
  • "Yo'q"ni tekshirishda NOT EXISTS: NOT IN ichida NULL bo'lsa, natija jimgina bo'sh.
  • Correlated ichki so'rov tashqi qatorga tayanadi — mantiqan har qator uchun qayta bajariladi.
  • FROM (...) — hosila jadval; LATERAL uni chapdagi qatorni ko'radigan qiladi — "har guruhdan N ta" uchun.

Keyingi dars: CTE (WITH) — ichki so'rovlarga nom berib, murakkab so'rovni bosqichlarga bo'lamiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
SQL subquery va LATERAL: IN, EXISTS, correlated va "har guruhdan oxirgi N ta" — IlmHamroh