Məzmuna keç
Educora
İrəli22 dəq13 / 22

Pəncərə funksiyaları

Sətirləri itirmədən reytinq qur, qruplar daxilində yer tut, artan cəm və əvvəlki sətirlə müqayisə hesabla: ROW_NUMBER, RANK, DENSE_RANK, SUM() OVER, PARTITION BY, LAG və LEAD.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • OVER (...) ifadəsinin GROUP BY-dan fərqini izah etmək
  • ROW_NUMBER, RANK və DENSE_RANK ilə reytinq qurmaq, PARTITION BY ilə qruplar daxilində sıralamaq
  • SUM() OVER (ORDER BY ...) ilə artan cəm, LAG və LEAD ilə qonşu sətirlərlə müqayisə hesablamaq

Müəllim hər kursun siyahısını istəyir: hər şagirdin balı, yanında isə kursun orta balı və şagirdin kursdakı yeri. GROUP BY bunu edə bilmir — o, sətirləri qrupa yığıb bir sətrə çevirir və adlar itir. Pəncərə funksiyaları isə hər sətri yerində saxlayır və ona qonşu sətirlərdən hesablanmış qiymət əlavə edir. Reytinqlər, artan cəmlər, «keçən aya görə dəyişmə» — analitikanın böyük hissəsi məhz bu alətlə yazılır.

OVER: sətirlərin «pəncərəsi»

Tərif
Pəncərə funksiyası

Hər sətir üçün həmin sətirlə əlaqəli sətirlər dəsti — pəncərə üzərində hesablanan funksiya. Yazılışı: funksiya(...) OVER (PARTITION BY ... ORDER BY ...). PARTITION BY pəncərəni qruplara bölür, ORDER BY isə pəncərənin içindəki sıranı müəyyən edir.

Adi aqreqatın arxasına OVER yazan kimi o, pəncərə funksiyasına çevrilir. Aşağıda hər yazılışın yanında həmin kursun orta balı görünür: sətirlər yığılmır, orta isə hər kurs üçün ayrıca hesablanır.

SQL
SELECT e.course_id, e.student_id, e.score,
       ROUND(AVG(e.score) OVER (PARTITION BY e.course_id), 1) AS course_avg
FROM enrollments AS e
WHERE e.course_id IN (1, 3)
ORDER BY e.course_id, e.score DESC;
▸ Gözlənilən nəticə
course_id | student_id | score | course_avg
1 | 11 | 97 | 87.3
1 | 1 | 92 | 87.3
1 | 5 | 85 | 87.3
1 | 2 | 75 | 87.3
3 | 11 | 94 | 80
3 | 2 | 81 | 80
3 | 6 | 77 | 80
3 | 4 | 68 | 80
GROUP BY course_id burada iki sətir verərdi; pəncərə funksiyası isə səkkiz sətrin hamısını saxlayır.

ROW_NUMBER, RANK və DENSE_RANK

Üç sıralama funksiyası yalnız bərabər qiymətlərdə fərqlənir. Şagirdləri yaşa görə sıralayaq: üç nəfərin 17 yaşı, üç nəfərin 16 yaşı var. ROW_NUMBER bərabərliyə baxmır və 1, 2, 3 verir, ona görə təkrarlanmayan ikinci sıralama sütunu (id) əlavə edirik ki, nəticə sabit olsun.

SQL
SELECT first_name, age,
       ROW_NUMBER() OVER (ORDER BY age DESC, id) AS row_num,
       RANK()       OVER (ORDER BY age DESC)     AS rnk,
       DENSE_RANK() OVER (ORDER BY age DESC)     AS dense
FROM students
ORDER BY age DESC, id
LIMIT 7;
▸ Gözlənilən nəticə
first_name | age | row_num | rnk | dense
Elvin | 17 | 1 | 1 | 1
Tural | 17 | 2 | 1 | 1
Fidan | 17 | 3 | 1 | 1
Murad | 16 | 4 | 4 | 2
Rəşad | 16 | 5 | 4 | 2
Səbinə | 16 | 6 | 4 | 2
Aysel | 15 | 7 | 7 | 3
FunksiyaBərabər qiymətlərdə17, 17, 16 üçün
ROW_NUMBER()hər sətrə ayrıca nömrə verir1, 2, 3
RANK()eyni yer verir, sonra boşluq qalır1, 1, 3
DENSE_RANK()eyni yer verir, boşluq qalmır1, 1, 2

PARTITION BY: hər qrupun öz reytinqi

PARTITION BY course_id hər kursu ayrıca pəncərəyə çevirir və nömrələmə hər kursda 1-dən başlayır. Bununla «hər kursun ən yaxşı şagirdi» sualını həll edirik: əvvəlcə CTE-də yerləri hesablayırıq, sonra pos = 1 olan sətirləri seçirik.

SQL
WITH ranked AS (
  SELECT c.title, s.first_name, e.score,
         RANK() OVER (PARTITION BY e.course_id ORDER BY e.score DESC) AS pos
  FROM enrollments AS e
  JOIN students AS s ON s.id = e.student_id
  JOIN courses  AS c ON c.id = e.course_id
)
SELECT title, first_name, score
FROM ranked
WHERE pos = 1
ORDER BY title;
▸ Gözlənilən nəticə
title | first_name | score
Algebra | Fidan | 97
English B1 | Leyla | 90
Geometry | Leyla | 95
Mechanics | Fidan | 94
Organic Chemistry | Elvin | 72
Python Basics | Rəşad | 99
World History | Nigar | 93

Artan cəm: SUM() OVER (ORDER BY ...)

OVER-in içinə ORDER BY yazanda pəncərə «əvvəldən bu sətrə qədər» olur. Ona görə SUM artan cəm verir: hər ayın gəliri əvvəlki ayların cəminə əlavə olunur. Aşağıda əvvəlcə CTE ilə aylıq gəliri hesablayırıq, sonra onun üzərində pəncərə funksiyası işlədirik.

SQL
WITH monthly AS (
  SELECT SUBSTR(o.order_date, 1, 7) AS month,
         ROUND(SUM(o.quantity * p.price), 2) AS revenue
  FROM orders AS o
  JOIN products AS p ON p.id = o.product_id
  GROUP BY month
)
SELECT month, revenue,
       ROUND(SUM(revenue) OVER (ORDER BY month), 2) AS running_total
FROM monthly
ORDER BY month;
▸ Gözlənilən nəticə
month | revenue | running_total
2025-01 | 1691 | 1691
2025-02 | 963.99 | 2654.99
2025-03 | 124.98 | 2779.97
2025-04 | 935.99 | 3715.96
2025-05 | 42 | 3757.96
2025-06 | 152.1 | 3910.06
2025-07 | 32 | 3942.06

Bir incəlik var: ORDER BY olanda susmaya görə çərçivə RANGE rejimindədir və sıralama açarı eyni olan sətirləri (həmyaşıdları) birlikdə götürür. Eyni gündə iki sifariş varsa, hər ikisi günün tam cəmini göstərir. Sətir-sətir artan cəm lazımdırsa, təkrarlanmayan açar əlavə et və ya çərçivəni açıq yaz: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.

SQL
SELECT id, order_date, quantity,
       SUM(quantity) OVER (ORDER BY order_date)     AS by_range,
       SUM(quantity) OVER (ORDER BY order_date, id) AS by_row
FROM orders
WHERE order_date < '2025-05-01'
ORDER BY order_date, id;
▸ Gözlənilən nəticə
id | order_date | quantity | by_range | by_row
1 | 2025-01-15 | 1 | 3 | 1
2 | 2025-01-15 | 2 | 3 | 3
3 | 2025-02-02 | 20 | 23 | 23
4 | 2025-02-10 | 1 | 24 | 24
5 | 2025-03-05 | 1 | 25 | 25
6 | 2025-03-18 | 2 | 27 | 27
7 | 2025-04-09 | 1 | 31 | 28
8 | 2025-04-09 | 3 | 31 | 31
1 və 2 nömrəli sifarişlər eyni gündədir: by_range hər ikisində 3 göstərir, by_row isə 1 və 3.

LAG və LEAD: qonşu sətirlə müqayisə

LAG(x) pəncərədə əvvəlki sətrin qiymətini, LEAD(x) isə növbəti sətrin qiymətini qaytarır. Bununla «keçən aya görə dəyişmə» bir sorğu ilə hesablanır. Birinci ayın əvvəlkisi olmadığı üçün LAG orada NULL verir. LAG(x, 2) iki sətir geriyə baxır, LAG(x, 1, 0) isə NULL əvəzinə 0 qaytarır.

SQL
WITH monthly AS (
  SELECT SUBSTR(o.order_date, 1, 7) AS month,
         ROUND(SUM(o.quantity * p.price), 2) AS revenue
  FROM orders AS o
  JOIN products AS p ON p.id = o.product_id
  GROUP BY month
)
SELECT month, revenue,
       LAG(revenue) OVER (ORDER BY month) AS prev_revenue,
       ROUND(revenue - LAG(revenue) OVER (ORDER BY month), 2) AS change
FROM monthly
ORDER BY month;
▸ Gözlənilən nəticə
month | revenue | prev_revenue | change
2025-01 | 1691 | NULL | NULL
2025-02 | 963.99 | 1691 | -727.01
2025-03 | 124.98 | 963.99 | -839.01
2025-04 | 935.99 | 124.98 | 811.01
2025-05 | 42 | 935.99 | -893.99
2025-06 | 152.1 | 42 | 110.1
2025-07 | 32 | 152.1 | -120.1
Tapşırıq

Hər kateqoriya daxilində məhsulları qiymətə görə sırala: ən baha məhsul 1-ci yeri alsın, yerlərdə boşluq qalmasın. category, name, price və price_rank sütunlarını kateqoriyaya, sonra yerə görə sıralanmış göstər.

Tapşırıq · SQL
SELECT category, name, price
       -- add price_rank here
FROM products
ORDER BY category, price DESC;
▸ Gözlənilən nəticə
category | name | price | price_rank
Accessories | Backpack | 55 | 1
Accessories | Water bottle | 12 | 2
Electronics | Laptop | 1450 | 1
Electronics | Smartphone | 899.99 | 2
Electronics | Monitor | 310 | 3
Electronics | Headphones | 120.5 | 4
Games | Chess set | 42 | 1
Home | Desk lamp | 34.99 | 1
Stationery | Pen set | 7.9 | 1
Stationery | Notebook | 3.2 | 2
Tapşırıq

1, 2 və 3 nömrəli müştərilərin sifarişləri üçün hər müştərinin öz sifariş nömrəsini (order_no: tarixə, sonra id-yə görə 1, 2, 3...) və həmin müştərinin artan ədəd cəmini (running_qty) hesabla. customer_id, order_date, quantity, order_no, running_qty sütunlarını müştəriyə və sifariş nömrəsinə görə sıralanmış göstər.

Tapşırıq · SQL
SELECT customer_id, order_date, quantity
       -- order_no and running_qty
FROM orders
WHERE customer_id IN (1, 2, 3)
ORDER BY customer_id, order_date, id;
▸ Gözlənilən nəticə
customer_id | order_date | quantity | order_no | running_qty
1 | 2025-01-15 | 1 | 1 | 1
1 | 2025-01-15 | 2 | 2 | 3
1 | 2025-07-07 | 10 | 3 | 13
2 | 2025-02-02 | 20 | 1 | 20
2 | 2025-06-30 | 1 | 2 | 21
3 | 2025-02-10 | 1 | 1 | 1
3 | 2025-05-21 | 1 | 2 | 2

Əsas fikirlər

  • Pəncərə funksiyası sətirləri yığmır: hər sətrə OVER (...) pəncərəsindən hesablanmış qiymət əlavə edir.
  • PARTITION BY pəncərəni qruplara bölür, ORDER BY isə onun içində sıra müəyyən edir.
  • Bərabərlikdə: ROW_NUMBER 1, 2, 3; RANK 1, 1, 3; DENSE_RANK 1, 1, 2.
  • SUM(x) OVER (ORDER BY ...) artan cəm verir; eyni açarlı sətirlər üçün ROWS çərçivəsi və ya təkrarlanmayan açar lazımdır.
  • LAG əvvəlki, LEAD növbəti sətrin qiymətini qaytarır; pəncərə funksiyasına görə süzmək üçün CTE və ya alt sorğu lazımdır.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
Pəncərə funksiyası ilə GROUP BY arasında əsas fərq nədir?