IlmHamroh
JavaScript Full-stack/26-qism. SQL va PostgreSQL29/64-dars17 daqiqa
Mundarija (29)

SQL window funksiyalar 2: LAG, LEAD, yig'ilib boruvchi jami va frame'lar

Qisqacha: LAG(x) — oldingi qatordagi qiymat, LEAD(x) — keyingisi: "kechagidan qancha o'sdi?" savoliga javob. sum(x) OVER (ORDER BY kun) — yig'ilib boruvchi jami (running total). Oynaning qaysi qatorlari hisobga kirishini frame belgilaydi: ROWS BETWEEN 2 PRECEDING AND CURRENT ROW — oxirgi 3 qator, RANGE BETWEEN interval '2 days' PRECEDING AND CURRENT ROW — oxirgi 3 kun. Eng ko'p uchraydigan tuzoq: ORDER BY li oynada sukut frame joriy qatorda tugaydi, shuning uchun last_value "oxirgi" emas, joriy qatorni qaytaradi.

Bu darsda

  • LAG va LEAD bilan qo'shni qatorlarni solishtirasiz.
  • Yig'ilib boruvchi jami va sirpanuvchi o'rtacha (moving average) hisoblaysiz.
  • ROWS va RANGE frame'lari farqini tenglar va bo'sh kunlar misolida ko'rasiz.
  • first_value/last_value tuzog'ini va uning yechimini bilasiz.
  • ntile, percent_rank va WINDOW bandini ishlatasiz.

Oldin bilishingiz kerak: Window funksiyalar 1: ranking, Ifodalar va o'rnatilgan funksiyalar (interval), CTE (WITH).

1. Nega bu kerak?

Window funksiyalar 1 darsida har qatorga guruh ichidagi o'rin berdik. Hisobotlarda esa boshqa savollar ko'p:

  • "Aziz yangi vazifani oldingisidan qancha vaqt o'tib yozdi?" — qo'shni qator bilan solishtirish.
  • "Oktyabr boshidan jami nechta vazifa yaratildi — har kun bo'yicha?" — yig'ilib boruvchi jami.
  • "Oxirgi 3 kunda kuniga o'rtacha nechta?" — sirpanuvchi o'rtacha, sakrashlarni tekislaydi.

Bularning hammasida bitta g'oya: har qator uchun oynaning bir qismi kerak — oldingi qator, boshidan shu qatorgacha, oxirgi 3 kun. Oynaning shu qismi frame (ramka) deyiladi. O'xshatish: poyezd derazasi. Butun yo'l — oyna (partition), derazadan ayni paytda ko'rinadigan bo'lak — frame. Poyezd yurgan sari deraza ham suriladi.

2. LAG va LEAD

2.1 Oldingi va keyingi qator

Azizning yuqori darajadagi vazifalari: har biri oldingisidan qancha vaqt o'tib yozilgan va undan keyin qaysi vazifa keldi (biz ishga tushirdik, PG 17.6, vazifalar holat 23):

sql
SELECT id, yaratilgan_vaqt,
       yaratilgan_vaqt - lag(yaratilgan_vaqt)
         OVER (ORDER BY yaratilgan_vaqt) AS oldingidan,
       lead(id) OVER (ORDER BY yaratilgan_vaqt) AS keyingi_id
FROM vazifalar WHERE egasi_id = 1 AND ota_id IS NULL
ORDER BY yaratilgan_vaqt;
text
 id |    yaratilgan_vaqt     |   oldingidan    | keyingi_id
----+------------------------+-----------------+------------
  1 | 2026-10-01 03:30:00+00 |                 |          2
  2 | 2026-10-01 04:10:00+00 | 00:40:00        |          8
  8 | 2026-10-02 13:40:00+00 | 1 day 09:30:00  |          9
  9 | 2026-10-03 02:55:00+00 | 13:15:00        |         17
 17 | 2026-10-05 16:00:00+00 | 2 days 13:05:00 |         23
 23 | 2026-10-07 05:15:00+00 | 1 day 13:15:00  |         28
 28 | 2026-10-10 07:00:00+00 | 3 days 01:45:00 |         34
 34 | 2026-10-14 14:30:00+00 | 4 days 07:30:00 |         38
 38 | 2026-10-18 12:20:00+00 | 3 days 21:50:00 |
(9 rows)

Kanon tajribasida xuddi shu so'rov lead(matn) bilan sinalgan (PostgreSQL 18.6) — telefon ekraniga sig'ishi uchun biz lead(id) qildik, vaqtlar bir xil.

  • lag(x) — oyna tartibida oldingi qatordagi x. Birinchi qatorda oldingi yo'q — NULL, shuning uchun oldingidan bo'sh.
  • lead(x) — keyingi qatordagi x. Oxirgi qatorda — NULL.

Natijadan ko'rinadi: Aziz boshida tez-tez yozgan (40 daqiqa, 13 soat), oxirida 3–4 kunda bir marta. Bu — "faollik pasaymoqda" signali.

2.2 Qadam va sukut qiymat

lag ning to'liq shakli: lag(x, qadam, sukut). lag(x, 2) — ikki qator oldingi. lag(x, 1, 0) — oldingi bo'lmasa NULL o'rniga 0. Masalan kunlik sonlar o'sishini hisoblashda birinchi kun uchun 0 qulayroq.

Tekshirib ko'ring: lead(yaratilgan_vaqt) OVER (ORDER BY yaratilgan_vaqt) - yaratilgan_vaqt nimani beradi va u yuqoridagi oldingidan ustunidan qanday farq qiladi?

Javob

Har vazifadan keyingisigacha qancha vaqt o'tganini beradi. Raqamlar o'sha, faqat bir qatorga yuqoriga siljigan: 1-vazifa qatorida 00:40:00, 2-da 1 day 09:30:00. Bo'sh katak birinchi qatorda emas, oxirgisida bo'ladi.

3. Yig'ilib boruvchi jami

3.1 Running total

Har kuni nechta vazifa yaratilgan va boshidan jami. Avval CTE'da kunlik sonlar, keyin ular ustida window:

sql
WITH kunlik AS (
  SELECT yaratilgan_vaqt::date AS kun, count(*) AS soni
  FROM vazifalar GROUP BY 1
)
SELECT kun, soni, sum(soni) OVER (ORDER BY kun) AS jami
FROM kunlik ORDER BY kun LIMIT 8;
text
    kun     | soni | jami
------------+------+------
 2026-10-01 |    3 |    3
 2026-10-02 |    7 |   10
 2026-10-03 |    6 |   16
 2026-10-04 |    2 |   18
 2026-10-05 |    6 |   24
 2026-10-06 |    4 |   28
 2026-10-07 |    2 |   30
 2026-10-08 |    2 |   32
(8 rows)

Kanon tajribasi. sum(soni) OVER (ORDER BY kun) — har qatorda: boshidan shu kungacha bo'lgan sonlar yig'indisi. Avvalgi darsda OVER () (tartibsiz) butun natija yig'indisini berardi. Oynaga ORDER BY qo'shilishi bilan frame "boshidan joriy qatorgacha" bo'ladi. Bu farq butun darsning kaliti.

Kunlik va yig'ilib boruvchi jami (1–8 oktyabr)
Vazifalar, ta
32218Oktyabr kunikunlik (soni): 1 → 3 takunlik (soni): 2 → 7 takunlik (soni): 3 → 6 takunlik (soni): 4 → 2 takunlik (soni): 5 → 6 takunlik (soni): 6 → 4 takunlik (soni): 7 → 2 takunlik (soni): 8 → 2 tajami (sum OVER): 1 → 3 tajami (sum OVER): 2 → 10 tajami (sum OVER): 3 → 16 tajami (sum OVER): 4 → 18 tajami (sum OVER): 5 → 24 tajami (sum OVER): 6 → 28 tajami (sum OVER): 7 → 30 tajami (sum OVER): 8 → 32 ta
  • kunlik (soni)
  • jami (sum OVER)
Kunlik va yig'ilib boruvchi jami (1–8 oktyabr)
Oktyabr kunikunlik (soni)jami (sum OVER)
13
27
36
42
56
64
72
82
13
210
316
418
524
628
730
832

Manba: vazifalar namuna bazasi (holat 23), kanon so'rovi, PostgreSQL 18.6

Grafikda ikki chiziqqa qarang: kunlik son sakraydi (7, 2, 6), jami esa doim o'sadi — u faqat kamaymaydi. Dashboard'lardagi "jami foydalanuvchilar" grafiklari aynan shu so'rov bilan chiziladi.

3.2 Sukut frame nima?

Postgres hujjatiga ko'ra, oynada ORDER BY bo'lsa, sukut frame:

sql
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

"Boshidan (cheklanmagan oldingi) joriy qatorgacha". ORDER BY bo'lmasa — butun partition. Frame'ni o'zingiz yozishingiz ham mumkin. Uning uch qismi bor:

Qism Variantlar
Rejim ROWS (qatorlar), RANGE (qiymat oralig'i), GROUPS (tenglar guruhi)
Boshi UNBOUNDED PRECEDING, n PRECEDING, CURRENT ROW
Oxiri CURRENT ROW, n FOLLOWING, UNBOUNDED FOLLOWING

PRECEDING — oldingi, FOLLOWING — keyingi, UNBOUNDED — chegarasiz (partition oxirigacha).

Diagrammada joriy qator — 3-qator. Sukut frame unga qadar bo'lgan hamma qatorlarni oladi, keyingilarini esa ko'rmaydi:

flowchart LR
  subgraph F["sukut frame: boshidan joriy qatorgacha"]
    Q1["1-qator"] --> Q2["2-qator"] --> Q3["3-qator (joriy)"]
  end
  Q3 --> Q4["4-qator: frame tashqarisida"]
  Q4 --> Q5["5-qator: frame tashqarisida"]

Joriy qator 4-qatorga o'tganda frame ham bir qatorga cho'ziladi — poyezd derazasi surildi.

4. ROWS va RANGE

4.1 Tenglarda farq

Sukut rejim — RANGE, va u tenglarni birga oladi. Bitta kunda bir nechta vazifa bo'lsa, ular oyna tartibida teng. Kanon tajribasi. Natijada 1-oktyabrning uch qatoriga qarang va ikki ustunni solishtiring:

sql
SELECT id, yaratilgan_vaqt::date AS kun,
  count(*) OVER (ORDER BY yaratilgan_vaqt::date) AS range_sukut,
  count(*) OVER (ORDER BY yaratilgan_vaqt::date
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS rows_frame
FROM vazifalar WHERE yaratilgan_vaqt < '2026-10-04'
ORDER BY yaratilgan_vaqt, id;
text
 id |    kun     | range_sukut | rows_frame
----+------------+-------------+------------
  1 | 2026-10-01 |           3 |          3
  2 | 2026-10-01 |           3 |          2
  3 | 2026-10-01 |           3 |          1
  4 | 2026-10-02 |          10 |          6
  5 | 2026-10-02 |          10 |          7
  6 | 2026-10-02 |          10 |          5
  7 | 2026-10-02 |          10 |          8
 41 | 2026-10-02 |          10 |          9
 42 | 2026-10-02 |          10 |         10
  8 | 2026-10-02 |          10 |          4
  9 | 2026-10-03 |          16 |         15
 10 | 2026-10-03 |          16 |         11
 11 | 2026-10-03 |          16 |         14
 12 | 2026-10-03 |          16 |         12
 46 | 2026-10-03 |          16 |         13
 47 | 2026-10-03 |          16 |         16
(16 rows)

range_sukut: 1-oktyabrning hamma uch qatorida 3 — RANGE uchun bir kundagi qatorlar "bitta nuqta", frame ularning hammasini oladi. rows_frame esa qatorma-qator sanaydi: 1, 2, 3, 4...

Lekin rows_frame raqamlariga diqqat qiling: 1-vazifada 3, 3-vazifada 1. Nega teskari? Oyna tartibi faqat kun bo'yicha. Bir kun ichidagi qatorlar tartibi kafolatlanmagan, Postgres ularni o'zi tanlagan tartibda sanadi. Bu Window funksiyalar 1 darsidagi ROW_NUMBER tuzog'ining o'zi: tenglarda ROWS tasodifiy. Kanon (PostgreSQL 18.6) va bizning sinov 17.6-bob aynan shu raqamlarni berdi, lekin bunga tayanib bo'lmaydi.

Qoida: running total uchun oyna tartibi noyob bo'lsin (ORDER BY yaratilgan_vaqt, id) — unda ROWS va RANGE bir xil va barqaror natija beradi.

4.2 Bo'sh kunlarda farq: "3 qator" va "3 kun"

Ikkinchi farq — oraliq bilan. Azizning kunlik vazifalari va ularning 3 kunlik yig'indisi ikki usulda (biz ishga tushirdik, PG 17.6):

sql
WITH kunlik AS (
  SELECT yaratilgan_vaqt::date AS kun, count(*) AS soni
  FROM vazifalar WHERE egasi_id = 1 GROUP BY 1
)
SELECT kun, soni,
  sum(soni) OVER (ORDER BY kun
    RANGE BETWEEN interval '2 days' PRECEDING AND CURRENT ROW)
    AS uch_kun,
  sum(soni) OVER (ORDER BY kun
    ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS uch_qator
FROM kunlik ORDER BY kun;
text
    kun     | soni | uch_kun | uch_qator
------------+------+---------+-----------
 2026-10-01 |    2 |       2 |         2
 2026-10-02 |    1 |       3 |         3
 2026-10-03 |    1 |       4 |         4
 2026-10-05 |    4 |       5 |         6
 2026-10-07 |    1 |       5 |         6
 2026-10-10 |    1 |       1 |         6
 2026-10-14 |    1 |       1 |         3
 2026-10-18 |    1 |       1 |         3
(8 rows)

Aziz har kuni yozmagan: 4, 6, 8, 9-oktyabrlar yo'q. 10-oktyabr qatoriga qarang:

Frame Nimani oldi Natija
RANGE ... interval '2 days' 8, 9, 10-oktyabr — faqat 10 bor 1
ROWS 2 PRECEDING oxirgi 3 qator: 5, 7, 10-oktyabr 6

"Oxirgi 3 kunda" degan savolga to'g'ri javob — RANGE (1). ROWS "oxirgi 3 ta yozuv" ni oldi va 5-oktyabrdagi 4 ta vazifani ham qo'shib yubordi. Qoida: vaqt bo'yicha oyna — RANGE + interval. Yozuvlar soni bo'yicha — ROWS. RANGE bilan n PRECEDING faqat bitta ORDER BY ustuni bilan ishlaydi (son, sana yoki vaqt).

Uchinchi rejim — GROUPS (PG 11+). U tenglar guruhini sanaydi: GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW — "joriy qiymat va undan oldingi bitta turli qiymat". Kunlik qatorlarga o'xshagan, lekin hali guruhlanmagan ma'lumotda qulay: har vazifa qatorida "bugun va oldingi faol kun" vazifalari. Amalda kam ishlatiladi, lekin hujjatda va boshqa kodda uchrasa, tanib olasiz.

4.3 Sirpanuvchi o'rtacha

sum o'rniga avg — moving average (sirpanuvchi o'rtacha). Hamma foydalanuvchilarning kunlik vazifalari, 3 kunlik o'rtacha bilan (kanon tajribasi):

sql
WITH kunlik AS (
  SELECT yaratilgan_vaqt::date AS kun, count(*) AS soni
  FROM vazifalar GROUP BY 1
)
SELECT kun, soni,
  round(avg(soni) OVER (ORDER BY kun
    RANGE BETWEEN interval '2 days' PRECEDING AND CURRENT ROW), 2)
    AS oxirgi_3_kun
FROM kunlik ORDER BY kun LIMIT 8;
text
    kun     | soni | oxirgi_3_kun
------------+------+--------------
 2026-10-01 |    3 |         3.00
 2026-10-02 |    7 |         5.00
 2026-10-03 |    6 |         5.33
 2026-10-04 |    2 |         5.00
 2026-10-05 |    6 |         4.67
 2026-10-06 |    4 |         4.00
 2026-10-07 |    2 |         4.00
 2026-10-08 |    2 |         2.67
(8 rows)

Kunlik son 3 → 7 → 6 → 2 sakraydi, o'rtacha esa 3.00 → 5.00 → 5.33 → 5.00 — silliq. Birinchi kunlarda frame to'liq emas (1-oktyabrda faqat o'zi) — o'rtacha kamroq kunlardan olinadi. Hisobotda buni hisobga oling yoki dastlabki kunlarni ko'rsatmang.

Diqqat: bu yerda count(*) ning bo'sh kunlari yo'q (hamma kunda vazifa bor). Bo'sh kunlar bo'lsa, ular kunlik da umuman qator bo'lmaydi va o'rtacha "ko'tarilib" ketadi. Bo'sh kunlarni 0 bilan to'ldirish — Tahliliy SQL naqshlari darsida generate_series bilan.

Tekshirib ko'ring: 7 kunlik sirpanuvchi o'rtacha uchun frame qanday yoziladi — ROWS bilanmi, RANGE bilanmi?

Javob

Kunlar bo'shliqsiz (har kun qatori bor) bo'lsa, ikkalasi bir xil: ROWS BETWEEN 6 PRECEDING AND CURRENT ROW yoki RANGE BETWEEN interval '6 days' PRECEDING AND CURRENT ROW. Bo'sh kunlar bo'lishi mumkin bo'lsa — faqat RANGE, aks holda "7 kun" emas, "7 ta yozuv" olinadi.

5. first_value, last_value va WINDOW bandi

5.1 last_value tuzog'i

Gulnoraning vazifalari: birinchisi va oxirgisi har qatorda (biz ishga tushirdik, PG 17.6; kanon tajribasi xuddi shu so'rovni matnlar bilan bajargan):

sql
SELECT id,
  first_value(id) OVER w AS birinchi,
  last_value(id) OVER w AS last_sukut,
  last_value(id) OVER (w ROWS BETWEEN UNBOUNDED PRECEDING
                         AND UNBOUNDED FOLLOWING) AS last_toliq
FROM vazifalar WHERE egasi_id = 6
WINDOW w AS (ORDER BY yaratilgan_vaqt)
ORDER BY yaratilgan_vaqt;
text
 id | birinchi | last_sukut | last_toliq
----+----------+------------+------------
  4 |        4 |          4 |         30
 12 |        4 |         12 |         30
 22 |        4 |         22 |         30
 30 |        4 |         30 |         30
(4 rows)

first_value to'g'ri: hamma joyda 4. last_value esa har qatorda o'zini qaytardi: 4, 12, 22, 30. Nega? Sukut frame "boshidan joriy qatorgacha". 12-vazifa qatorida frame = {4, 12}, uning oxirgisi — 12. Funksiya to'g'ri ishladi, frame biz kutgandek emas edi.

Yechim — frame'ni partition oxirigacha cho'zish: ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING. Shunda hamma qatorda 30. Ko'pincha undan ham soddasi bor: oyna tartibini teskari qilib, first_value ishlatish.

Xuddi shu tuzoq nth_value(x, n) da ham bor ("oynadagi n-qiymat"): sukut frame'da u n-qatorga yetmaguncha NULL beradi.

5.2 WINDOW bandi

Yuqoridagi so'rovda yangi narsa: WINDOW w AS (...) — oynaga nom berish. Bir xil oyna uch joyda kerak bo'lsa, uni bir marta yozib, OVER w deb murojaat qilasiz. OVER (w ROWS BETWEEN ...) — nomli oynani olib, unga frame qo'shish. WINDOW bandi HAVING dan keyin, ORDER BY dan oldin yoziladi.

Bu CTE'ga o'xshash g'oya — takrorni nom bilan almashtirish. Oyna tartibini o'zgartirish kerak bo'lsa, bitta joyda o'zgartirasiz.

6. ntile va percent_rank

Bajarilgan vazifalar — qanchalik tez bajarilgani bo'yicha. Kanon tajribasida 22 qator bor edi, bu yerda birinchi 8 tasi va matn o'rniga id (biz ishga tushirdik, PG 17.6):

sql
SELECT id, bajarilgan_vaqt - yaratilgan_vaqt AS vaqt,
  ntile(4) OVER (ORDER BY bajarilgan_vaqt - yaratilgan_vaqt)
    AS chorak,
  round(percent_rank() OVER (ORDER BY bajarilgan_vaqt
        - yaratilgan_vaqt)::numeric, 2) AS foiz_rank
FROM vazifalar WHERE bajarildi ORDER BY vaqt, id LIMIT 8;
text
 id |   vaqt   | chorak | foiz_rank
----+----------+--------+-----------
 28 | 00:05:00 |      1 |      0.00
 37 | 00:10:00 |      1 |      0.05
 15 | 00:30:00 |      1 |      0.10
  8 | 00:40:00 |      1 |      0.14
  4 | 00:45:00 |      1 |      0.19
 16 | 01:00:00 |      1 |      0.24
 20 | 01:00:00 |      2 |      0.24
 35 | 01:30:00 |      2 |      0.33
(8 rows)
  • ntile(4) — qatorlarni 4 ta teng guruhga bo'ladi (chorak, quartile). 22 qator / 4 = 5.5 — birinchi ikki guruhda 6 tadan, qolganlarida 5 tadan. 1-chorak — eng tez bajarilganlar.
  • percent_rank() — nisbiy o'rin 0 dan 1 gacha: (rank - 1) / (qatorlar - 1). 16-vazifada rank = 6, demak (6 − 1) / (22 − 1) = 5 / 21 ≈ 0.24: "qolgan 21 vazifadan 5 tasi (taxminan 24 %) bundan tezroq bajarilgan".

16 va 20-vazifalarni solishtiring. Ikkalasi ham 1 soatda bajarilgan, lekin biri 1-chorakda, biri 2-chorakda. ntile guruhlarni teng qilish uchun tenglarni ham ajratadi. percent_rank esa tenglarga bir xil qiymat beradi 0.24-bob — u RANK ga asoslangan.

cume_dist() — o'xshash funksiya: "shu qiymatgacha va shu qiymatni o'z ichiga olib necha foiz". Mediana kabi foiz chegaralari uchun esa window emas, agregat bor: percentile_cont(0.5) WITHIN GROUP (ORDER BY ...).

7. Ko'p uchraydigan xatolar

7.1 last_value joriy qatorni qaytaradi

Sukut frame joriy qatorda tugaydi. ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING qo'shing yoki teskari tartib bilan first_value.

7.2 Running total "sakraydi"

Oyna tartibida tenglar bor va sukut RANGE ularni birga qo'shdi. Tartibga noyob ustun qo'shing.

7.3 "Oxirgi 3 kun" noto'g'ri

ROWS 2 PRECEDING — 3 kun emas, 3 qator. Vaqt oynasi uchun RANGE ... interval.

7.4 window functions are not allowed in WHERE

sql
SELECT id, matn FROM vazifalar WHERE egasi_id = 1 AND id = 23
  AND lag(id) OVER (ORDER BY id) IS NOT NULL;
text
ERROR:  window functions are not allowed in WHERE
LINE 2:   AND lag(id) OVER (ORDER BY id) IS NOT NULL;
              ^

Window natijasi bilan filtrlash — CTE orqali (4-mashqda ko'rasiz). Bu holda yana bitta tuzoq bor: WHERE id = 23 window'dan oldin ishlaydi. CTE'siz yozsangiz, oynada faqat bitta qator qoladi va lag doim NULL bo'ladi.

8. Mashqlar

Mashq fayllari kurs/mashqlar/26/29-window-qiymat/ da. Hammasi vazifalar holat 23 da.

1-mashq (oson): oldingi vazifa

Bekzodning (egasi_id = 7) vazifalari yaratilish tartibida: id, matn va oldingi vazifaning id si. Yordam: lag(id) OVER (ORDER BY yaratilgan_vaqt).

Yechim
sql
SELECT id, matn, lag(id) OVER (ORDER BY yaratilgan_vaqt) AS oldingi
FROM vazifalar WHERE egasi_id = 7 ORDER BY yaratilgan_vaqt;
text
 id |               matn               | oldingi
----+----------------------------------+---------
 19 | Universitet kontrakti #to'lov    |
 25 | Algoritmlar: 5 ta masala #o'qish |      19
 33 | Noutbukka SSD qo'yish #ta'mir    |      25
 39 | Do'stlar bilan choyxona #dam     |      33
(4 rows)

Birinchi vazifaning oldingisi yo'q — NULL.

2-mashq (o'rta): bajarilganlar jamg'armasi

Har kuni nechta vazifa bajarilgan va boshidan jami — birinchi 6 kun. Yordam: «Running total» bo'limidagi so'rov, yaratilgan_vaqt o'rniga bajarilgan_vaqt va WHERE bajarildi.

Yechim
sql
WITH kunlik AS (
  SELECT bajarilgan_vaqt::date AS kun, count(*) AS soni
  FROM vazifalar WHERE bajarildi GROUP BY 1
)
SELECT kun, soni, sum(soni) OVER (ORDER BY kun) AS jami
FROM kunlik ORDER BY kun LIMIT 6;
text
    kun     | soni | jami
------------+------+------
 2026-10-01 |    2 |    2
 2026-10-02 |    2 |    4
 2026-10-03 |    2 |    6
 2026-10-04 |    1 |    7
 2026-10-05 |    2 |    9
 2026-10-06 |    2 |   11
(6 rows)

kun — CTE'dagi noyob qiymat (har kun bitta qator), shuning uchun sukut RANGE bilan ham tenglar muammosi yo'q.

3-mashq (qiyin): tanaffuslar

Aziz vazifa yozgan kunlar va har biri oldingi faol kundan necha kun keyin kelgani: kun, oldingi, farq_kun. Yordam: CTE'da DISTINCT yaratilgan_vaqt::date, keyin lag(kun) va ikki sananing ayirmasi (date - date — kunlar soni, butun son).

Yechim
sql
WITH kunlar AS (
  SELECT DISTINCT yaratilgan_vaqt::date AS kun
  FROM vazifalar WHERE egasi_id = 1
)
SELECT kun, lag(kun) OVER (ORDER BY kun) AS oldingi,
       kun - lag(kun) OVER (ORDER BY kun) AS farq_kun
FROM kunlar ORDER BY kun;
text
    kun     |  oldingi   | farq_kun
------------+------------+----------
 2026-10-01 |            |
 2026-10-02 | 2026-10-01 |        1
 2026-10-03 | 2026-10-02 |        1
 2026-10-05 | 2026-10-03 |        2
 2026-10-07 | 2026-10-05 |        2
 2026-10-10 | 2026-10-07 |        3
 2026-10-14 | 2026-10-10 |        4
 2026-10-18 | 2026-10-14 |        4
(8 rows)

farq_kun = 1 — ketma-ket kunlar. 1–3 oktyabr — Azizning uch kunlik "seriyasi" (streak). Ilovalardagi "5 kun ketma-ket o'qidingiz" belgisi shunday hisoblanadi. Seriyalarni guruhlashning to'liq usuli — "gaps and islands", Tahliliy SQL naqshlari darsida.

4-mashq: SQL qadami — "oldingi / keyingi" tugmalari

Vazifa sahifasida "← oldingi" va "keyingi →" tugmalari bo'ladi. Azizning yuqori darajadagi vazifalari (ota_id IS NULL) yaratilish tartibida: 23-vazifaning oldingisi va keyingisining id sini toping. Sxema o'zgarmaydi — commit yo'q. Yordam: CTE'da lag/lead hamma qatorlar uchun, tashqarida WHERE id = 23 («window functions are not allowed in WHERE» xatosini eslang).

Yechim
sql
WITH t AS (
  SELECT id,
         lag(id)  OVER (ORDER BY yaratilgan_vaqt, id) AS oldingi,
         lead(id) OVER (ORDER BY yaratilgan_vaqt, id) AS keyingi
  FROM vazifalar WHERE egasi_id = 1 AND ota_id IS NULL
)
SELECT * FROM t WHERE id = 23;
text
 id | oldingi | keyingi
----+---------+---------
 23 |      17 |      28
(1 row)

Filtr WHERE id = 23 CTE tashqarisida — aks holda oynada bitta qator qolib, ikkala qo'shni ham NULL bo'lardi. Oyna tartibida id — tenglar uchun. 2 mln vazifali bazada bu so'rov hamma qatorlarni raqamlaydi; katta jadvalda "oldingi" ni WHERE yaratilgan_vaqt < ... ORDER BY ... DESC LIMIT 1 bilan topish tezroq (Keyset pagination darsidagi g'oya).

9. Real ishda

  • Moliya va savdo: kunlik tushum va oy boshidan jami (running total), "kechagiga nisbatan +12 %" (lag), 7 kunlik o'rtacha — har dashboard'da.
  • Mahsulot analitikasi: foydalanuvchi sessiyalari orasidagi vaqt (lag), "ketma-ket kunlar" seriyasi, birinchi va oxirgi harakat (first_value).
  • Monitoring: server javob vaqtining sirpanuvchi o'rtachasi va 95-persentili.
  • Valyuta kurslari: 24-qismdagi valyuta vositasidagi kunlik kurs o'zgarishi — bazada lag(kurs) OVER (PARTITION BY valyuta ORDER BY sana).
  • Intervyu: "oylik savdoning o'tgan oyga nisbatan o'sishini toping", "ROWS va RANGE farqi" — tez-tez so'raladi.

Xulosa

  • lag/lead — oldingi/keyingi qator qiymati; chetida NULL (yoki uchinchi argument).
  • sum(x) OVER (ORDER BY ...) — yig'ilib boruvchi jami: ORDER BY li oynada sukut frame "boshidan joriy qatorgacha".
  • ROWS — qatorlar soni, RANGE — qiymat oralig'i (tenglar birga, interval bilan vaqt oynasi).
  • last_value sukut frame'da joriy qatorni beradi — frame'ni UNBOUNDED FOLLOWING gacha cho'zing.
  • ntile(n) — teng guruhlar, percent_rank — 0…1 nisbiy o'rin; WINDOW w AS (...) — oynaga nom.

Keyingi dars: To'plam amallari — UNION, INTERSECT va EXCEPT bilan ikki so'rov natijasini birlashtiramiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
SQL window funksiyalar 2: LAG, LEAD, yig'ilib boruvchi jami va frame'lar — IlmHamroh