📈 Funkcje Analityczne

RANK, DENSE_RANK, LAG/LEAD, okna, WIDTH_BUCKET, FIRST/LAST_VALUE

⚡ Kluczowe fakty do zapamiętania
  • Funkcje analityczne NIE redukują wierszy — w odróżnieniu od GROUP BY. Tabela wyjściowa ma TYLE SAMO wierszy co wejściowa. Każdy wiersz dostaje dodatkową kolumnę z wynikiem obliczonym na podstawie okna.
  • RANK() = ranking z lukami przy remisach: pensje [3000, 3000, 5000] dają rangi [1, 1, 3] (pomija 2). DENSE_RANK() = bez luk: [1, 1, 2]. ROW_NUMBER() = zawsze unikalne, arbitralne przy remisach: [1, 2, 3].
  • Pułapka LAST_VALUE! Domyślna ramka okna to RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Dla LAST_VALUE to znaczy: 'ostatni spośród wierszy od początku do MNIE' — czyli ja sam! Żeby dostać prawdziwy ostatni, musisz napisać: ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.
  • LAG(kolumna, N, domyślna) = patrzy N wierszy WSTECZ wg ORDER BY. Gdy nie ma N-tego poprzednika (np. pierwsze dwa wiersze dla LAG offset=2), zwraca wartość domyślną. LEAD = patrzy N wierszy NAPRZÓD.
  • RATIO_TO_REPORT(x) OVER (PARTITION BY ...) = x podzielone przez sumę x w grupie. Wynik w przedziale [0,1]. Suma wszystkich wyników w partycji = 1. Szybszy i czytelniejszy odpowiednik: x/SUM(x) OVER(...).
  • RANK(5000) WITHIN GROUP (ORDER BY salary) = ranking hipotetyczny: 'na której pozycji znalazłaby się pensja 5000 gdybyśmy ją wstawili do tabeli?' Nie jest to funkcja analityczna — nie ma OVER(), to funkcja porządkująca.
  • Funkcje analityczne w klauzulach: można użyć w SELECT i ORDER BY. NIGDY w WHERE, GROUP BY ani HAVING. Żeby filtrować po wynikach funkcji analitycznej, musisz użyć podzapytania.

Składnia ogólna

sql
funkcja_analityczna() OVER (
    [PARTITION BY kolumny]     -- podział na grupy (opcjonalny)
    [ORDER BY kolumny]         -- kolejność w ramach grupy
    [ROWS/RANGE BETWEEN ...]   -- ramka okna (opcjonalna)
)

Funkcje analityczne NIE redukują wierszy (w przeciwieństwie do GROUP BY). Każdy wiersz zachowuje swoją tożsamość i dostaje dodatkową kolumnę z wynikiem.

RANK vs DENSE_RANK vs ROW_NUMBER

sql
SELECT first_name, last_name, salary,
    ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn,
    RANK()       OVER (ORDER BY salary DESC) AS rnk,
    DENSE_RANK() OVER (ORDER BY salary DESC) AS drnk
FROM hr.employees WHERE department_id = 60;
Wynik — dział 60 (IT), posortowany wg salary DESC
LAST_NAMESALARYROW_NUMBERRANKDENSE_RANK
Hunold9000111
Ernst6000222
Austin4800333
Pataballa4800433
Lorentz4200554
UWAGA na wiersze 3 i 4: Austin i Pataballa mają tę samą pensję 4800. ROW_NUMBER daje im 3 i 4 (arbitralnie). RANK daje oba rank=3, ale POMIJA 4 → Lorentz dostaje rank=5. DENSE_RANK daje oba rank=3 i NIE pomija — Lorentz=4. Właśnie to jest różnica!
sql
-- TOP 3 zarabiający w każdym dziale (RANK - z lukami):
SELECT * FROM (
    SELECT first_name, last_name, salary, department_id,
           RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rnk
    FROM hr.employees
)
WHERE rnk <= 3;

NTILE — podział na kwantyle

sql
-- Podziel pracowników na 4 grupy (kwartyle) wg pensji
SELECT first_name, last_name, salary,
       NTILE(4) OVER (ORDER BY salary) AS kwartal
FROM hr.employees;
-- Wynik: każdy pracownik dostaje numer 1, 2, 3 lub 4

CUME_DIST i PERCENT_RANK

sql
SELECT first_name, salary,
    CUME_DIST()    OVER (ORDER BY salary) AS cume_dist,   -- frakcja [0,1] <= wartość
    PERCENT_RANK() OVER (ORDER BY salary) AS pct_rank     -- frakcja [0,1] bez bieżącej
FROM hr.employees;

-- Pracownicy w górnych 20% pensji:
SELECT first_name, salary FROM (
    SELECT first_name, salary,
           CUME_DIST() OVER (ORDER BY salary) AS cd
    FROM hr.employees
)
WHERE cd >= 0.8;

Ramki okna — ROWS vs RANGE — wyniki

⚖️ Porównanie wynikówSUM OVER z i bez ORDER BY
SUM(salary) OVER (PARTITION BY dept_id)
NAMEDEPTSALARYSUM_OVER
Hunold60900028000
Ernst60600028000
Austin60480028000
Pataballa60480028000
Lorentz60420028000
SUM(salary) OVER (PARTITION BY dept_id ORDER BY salary)
NAMEDEPTSALARYSUM_OVER
Lorentz6042004200
Austin60480013800
Pataballa60480013800
Ernst60600019800
Hunold60900028000
💡 Bez ORDER BY w OVER → suma całej partycji (28000) dla każdego wiersza. Z ORDER BY → suma narastająca (running total)! Domyślna ramka z ORDER BY to UNBOUNDED PRECEDING TO CURRENT ROW — klasyczna pułapka.

LAG i LEAD — dostęp do sąsiednich wierszy

sql
-- LAG(kolumna, offset, default) — poprzedni wiersz
-- LEAD(kolumna, offset, default) — następny wiersz
SELECT employee_id, hire_date, salary,
    LAG(salary, 1, 0)  OVER (ORDER BY hire_date) AS poprzednia_pensja,
    LEAD(salary, 1, 0) OVER (ORDER BY hire_date) AS nastepna_pensja,
    salary - LAG(salary, 1, salary) OVER (ORDER BY hire_date) AS roznica
FROM hr.employees;
Wynik LAG i LEAD — widać 'przesunięcie' o 1 wiersz
HIRE_DATENAMESALARYLAG (poprzedni)LEAD (następny)RÓŻNICA
1987-06-17King240000170000
1989-09-21Kochhar170002400017000-7000
1993-01-13De Haan1700017000120080
1994-08-17Greenberg12008170009000-4992
1997-09-30Hunold900012008NULL-3008
Pierwszy wiersz: LAG=0 (brak poprzednika — zwraca wartość domyślną 0). Ostatni wiersz: LEAD=NULL (brak następnika — gdy nie podasz domyślnej). RÓŻNICA = salary - LAG(salary, 1, salary): dla pierwszego wiersza LAG=salary więc różnica=0.

Ramki okna — ROWS vs RANGE

sql
-- Suma bieżąca (running total):
SELECT hire_date, salary,
    SUM(salary) OVER (ORDER BY hire_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS suma_biezaca
FROM hr.employees;

-- Średnia krocząca 3-miesięczna:
SUM(wartosc) OVER (ORDER BY data ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)

-- Dostępne zakresy:
-- UNBOUNDED PRECEDING  — od początku partycji
-- N PRECEDING          — N wierszy wstecz
-- CURRENT ROW
-- N FOLLOWING          — N wierszy naprzód
-- UNBOUNDED FOLLOWING  — do końca partycji
⚠️
ROWS vs RANGE
ROWS — liczy fizyczne wiersze (dokładne, szybsze)
RANGE — grupuje wiersze o tej samej wartości ORDER BY razem (może dać nieoczekiwane wyniki przy powtórzeniach)
Domyślnie bez ROWS/RANGE: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

FIRST_VALUE i LAST_VALUE

sql
-- Najwyższa pensja w dziale (dla każdego wiersza)
SELECT first_name, salary, department_id,
    FIRST_VALUE(salary) OVER (
        PARTITION BY department_id
        ORDER BY salary DESC
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS max_w_dziale,
    LAST_VALUE(salary) OVER (
        PARTITION BY department_id
        ORDER BY salary DESC
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS min_w_dziale
FROM hr.employees;
🚨
LAST_VALUE — pułapka z domyślną ramką
Domyślna ramka to RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Dla LAST_VALUE to oznacza "ostatni w dotychczas widzianych wierszach", nie "ostatni w całej grupie"! Zawsze dodawaj ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING przy LAST_VALUE.
⚖️ Porównanie wynikówLAST_VALUE — z i bez ROWS BETWEEN
LAST_VALUE bez ROWS BETWEEN (BŁĄD!)
NAMESALARYLAST_VAL
Lorentz42004200
Austin48004800
Pataballa48004800
Ernst60006000
Hunold90009000
LAST_VALUE z ROWS BETWEEN UNBOUNDED (POPRAWNIE)
NAMESALARYLAST_VAL
Lorentz42009000
Austin48009000
Pataballa48009000
Ernst60009000
Hunold90009000
💡 Bez ROWS BETWEEN: LAST_VALUE widzi okno od początku DO BIEŻĄCEGO WIERSZA — więc 'ostatni' to zawsze bieżący wiersz (sam siebie). Dopiero z ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING: okno to cała partycja → LAST_VALUE = Hunold (9000) dla każdego.

RATIO_TO_REPORT — udział procentowy

sql
-- Udział pensji pracownika w sumie pensji działu
SELECT first_name, salary, department_id,
    RATIO_TO_REPORT(salary) OVER (PARTITION BY department_id) AS udzial
FROM hr.employees;
-- Wynik: liczba z zakresu [0, 1], suma w grupie = 1.0

WIDTH_BUCKET — histogramy

sql
-- Podziel pensje na 5 równych przedziałów (2000–15000)
SELECT first_name, salary,
    WIDTH_BUCKET(salary, 2000, 15000, 5) AS bucket
FROM hr.employees;
-- bucket 0 = poniżej min, bucket 6 = powyżej max
-- bucket 1 = [2000, 4600), bucket 2 = [4600, 7200), itd.

Ranking hipotetyczny — RANK(x) WITHIN GROUP

Odpowiada na pytanie: "Na której pozycji znalazłby się wiersz z wartością X, gdyby został wstawiony do istniejącego zbioru?"

sql
-- Jaką pozycję miałby pracownik z pensją 10000?
SELECT RANK(10000) WITHIN GROUP (ORDER BY salary) AS pozycja
FROM hr.employees;
-- Wynik: np. 85 — 84 pracowników zarabia mniej niż 10000

-- DENSE_RANK — bez luk w rankingu:
SELECT DENSE_RANK(10000) WITHIN GROUP (ORDER BY salary) AS dense_poz
FROM hr.employees;

-- PERCENT_RANK — ile procent wyników jest poniżej 10000?
SELECT PERCENT_RANK(10000) WITHIN GROUP (ORDER BY salary) AS pct
FROM hr.employees;

-- CUME_DIST — ile procent wyników jest <= 10000?
SELECT CUME_DIST(10000) WITHIN GROUP (ORDER BY salary) AS cd
FROM hr.employees;

-- Na zagregowanych danych — na której pozycji byłby dział z sumą 50000?
SELECT RANK(50000) WITHIN GROUP (ORDER BY SUM(salary)) AS pozycja_dzialu,
       PERCENT_RANK(50000) WITHIN GROUP (ORDER BY SUM(salary)) AS procent
FROM hr.employees
GROUP BY department_id;

WITHIN GROUP — funkcje porządkujące

sql
-- Mediana pensji (percentyl 50%):
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS mediana
FROM hr.employees;

-- Percentyl 90%:
SELECT PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY salary) AS p90
FROM hr.employees;

-- LISTAGG — agregacja do stringa:
SELECT department_id,
    LISTAGG(last_name, ', ') WITHIN GROUP (ORDER BY last_name) AS pracownicy
FROM hr.employees
GROUP BY department_id;

SH schema — wzorce analityczne

SH (Sales History) — schematy faktów i wymiarów Oracle. Tabele: sh.sales, sh.products, sh.customers, sh.times, sh.channels, sh.costs.

SUM OVER — sumy globalne i per kanał
-- Globalna suma sprzedaży (jeden wiersz na cały wynik):
SELECT channel_id, SUM(amount_sold) AS sprzedaz,
    SUM(SUM(amount_sold)) OVER () AS suma_globalna
FROM sh.sales
GROUP BY channel_id;

-- Udział kanału w globalnej sprzedaży:
SELECT channel_id, SUM(amount_sold) AS sprzedaz,
    ROUND(SUM(amount_sold) / SUM(SUM(amount_sold)) OVER () * 100, 2) AS udzial_pct
FROM sh.sales
GROUP BY channel_id
ORDER BY sprzedaz DESC;
Suma bieżąca i RANK na zagregowanych danych
-- Ranking kanałów wg sprzedaży + suma bieżąca (od największego):
SELECT channel_id,
    SUM(amount_sold) AS sprzedaz,
    RANK() OVER (ORDER BY SUM(amount_sold) DESC) AS rank_kanal,
    SUM(SUM(amount_sold)) OVER (ORDER BY SUM(amount_sold) DESC
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS suma_biezaca
FROM sh.sales
GROUP BY channel_id
ORDER BY sprzedaz DESC;
ROW_NUMBER — TOP N produktów
-- TOP 5 produktów globalnie wg przychodów:
SELECT prod_id, sprzedaz, rn FROM (
    SELECT prod_id,
        SUM(amount_sold) AS sprzedaz,
        ROW_NUMBER() OVER (ORDER BY SUM(amount_sold) DESC) AS rn
    FROM sh.sales
    GROUP BY prod_id
)
WHERE rn <= 5;
RANK per kanał z PARTITION BY
-- TOP 5 produktów w każdym kanale sprzedaży:
SELECT * FROM (
    SELECT s.prod_id, s.channel_id,
        SUM(s.amount_sold) AS sprzedaz,
        RANK() OVER (PARTITION BY s.channel_id
                     ORDER BY SUM(s.amount_sold) DESC) AS rank_w_kanale
    FROM sh.sales s
    GROUP BY s.prod_id, s.channel_id
)
WHERE rank_w_kanale <= 5
ORDER BY channel_id, rank_w_kanale;
LAG — porównanie okresu do okresu
-- Sprzedaż miesięczna 2001: zmiana vs poprzedni miesiąc:
SELECT t.calendar_month_number AS miesiac,
    SUM(s.amount_sold) AS sprzedaz,
    LAG(SUM(s.amount_sold)) OVER (ORDER BY t.calendar_month_number) AS poprzedni_mies,
    SUM(s.amount_sold) - LAG(SUM(s.amount_sold), 1, 0)
        OVER (ORDER BY t.calendar_month_number) AS zmiana
FROM sh.sales s
JOIN sh.times t ON s.time_id = t.time_id
WHERE t.calendar_year = 2001
GROUP BY t.calendar_month_number
ORDER BY t.calendar_month_number;
AVG OVER PARTITION — odchylenie od średniej działu
-- Każdy produkt vs średnia sprzedaż w jego kategorii:
SELECT p.prod_name, p.prod_category,
    SUM(s.amount_sold) AS sprzedaz,
    AVG(SUM(s.amount_sold)) OVER (PARTITION BY p.prod_category) AS sr_w_kat,
    SUM(s.amount_sold) - AVG(SUM(s.amount_sold))
        OVER (PARTITION BY p.prod_category) AS odchylenie
FROM sh.sales s
JOIN sh.products p ON s.prod_id = p.prod_id
GROUP BY p.prod_name, p.prod_category
ORDER BY p.prod_category, sprzedaz DESC;
RATIO_TO_REPORT — udziały w SH
-- Udział każdego kanału w całkowitej sprzedaży:
SELECT channel_id,
    SUM(amount_sold) AS sprzedaz,
    ROUND(RATIO_TO_REPORT(SUM(amount_sold)) OVER () * 100, 2) AS udzial_pct
FROM sh.sales
GROUP BY channel_id
ORDER BY sprzedaz DESC;
Ranking + JOIN z tabelami wymiarów
-- TOP 5 produktów wg sprzedaży w każdej kategorii (z nazwami):
SELECT * FROM (
    SELECT p.prod_category, p.prod_name,
           SUM(s.amount_sold) AS sprzedaz,
           RANK() OVER (PARTITION BY p.prod_category
                        ORDER BY SUM(s.amount_sold) DESC) AS rnk
    FROM sh.sales s
    JOIN sh.products p ON s.prod_id = p.prod_id
    GROUP BY p.prod_category, p.prod_name
)
WHERE rnk <= 5
ORDER BY prod_category, rnk;

-- Ranking produktów w kanale z nazwą kanału:
SELECT * FROM (
    SELECT ch.channel_desc, p.prod_name,
           SUM(s.amount_sold) AS sprzedaz,
           RANK() OVER (PARTITION BY s.channel_id
                        ORDER BY SUM(s.amount_sold) DESC) AS rank_w_kanale
    FROM sh.sales s
    JOIN sh.products p   ON s.prod_id   = p.prod_id
    JOIN sh.channels ch  ON s.channel_id = ch.channel_id
    GROUP BY ch.channel_desc, p.prod_name, s.channel_id
)
WHERE rank_w_kanale <= 3;
NTILE — segmentacja klientów
-- Podziel klientów na 4 kwartyle wg sumy zakupów:
SELECT cust_id,
    SUM(amount_sold) AS suma_zakupow,
    NTILE(4) OVER (ORDER BY SUM(amount_sold) DESC) AS segment
FROM sh.sales
GROUP BY cust_id
ORDER BY suma_zakupow DESC;

-- Średnia sprzedaż w 5 segmentach per miasto (z joinami):
SELECT c.cust_city, seg, AVG(suma_zakupow) AS sr_w_segmencie FROM (
    SELECT s.cust_id, c.cust_city,
        SUM(s.amount_sold) AS suma_zakupow,
        NTILE(5) OVER (PARTITION BY c.cust_city
                       ORDER BY SUM(s.amount_sold) DESC) AS seg
    FROM sh.sales s
    JOIN sh.customers c ON s.cust_id = c.cust_id
    GROUP BY s.cust_id, c.cust_city
)
GROUP BY cust_city, seg
ORDER BY cust_city, seg;
Średnia krocząca — ROWS BETWEEN i RANGE INTERVAL
-- Średnia krocząca 7 dni (7 poprzednich wierszy):
SELECT time_id, SUM(amount_sold) AS dzienna_sprzedaz,
    ROUND(AVG(SUM(amount_sold)) OVER (
        ORDER BY time_id
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ), 2) AS srednia_7_dni
FROM sh.sales
GROUP BY time_id
ORDER BY time_id;

-- Średnia krocząca 7 dni wg wartości daty (RANGE):
SELECT time_id, SUM(amount_sold) AS dzienna_sprzedaz,
    ROUND(AVG(SUM(amount_sold)) OVER (
        ORDER BY time_id
        RANGE BETWEEN INTERVAL '7' DAY PRECEDING AND CURRENT ROW
    ), 2) AS srednia_zakres_7d
FROM sh.sales
GROUP BY time_id
ORDER BY time_id;
FIRST_VALUE / LAST_VALUE — wartości skrajne
-- Sprzedaż w każdym roku vs rok z najwyższą i najniższą sprzedażą:
SELECT t.calendar_year AS rok,
    SUM(s.amount_sold) AS sprzedaz,
    FIRST_VALUE(SUM(s.amount_sold)) OVER (
        ORDER BY SUM(s.amount_sold) DESC
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS max_rok_sprzedaz,
    LAST_VALUE(SUM(s.amount_sold)) OVER (
        ORDER BY SUM(s.amount_sold) DESC
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS min_rok_sprzedaz
FROM sh.sales s
JOIN sh.times t ON s.time_id = t.time_id
GROUP BY t.calendar_year
ORDER BY rok;
Analiza trendu + LAG + roczny ranking
-- Sprzedaż roczna z trendem i rankingiem:
SELECT t.calendar_year AS rok,
    SUM(s.amount_sold) AS sprzedaz,
    LAG(SUM(s.amount_sold)) OVER (ORDER BY t.calendar_year) AS rok_poprzedni,
    SUM(s.amount_sold) - LAG(SUM(s.amount_sold), 1, 0)
        OVER (ORDER BY t.calendar_year) AS zmiana_rdr,
    RANK() OVER (ORDER BY SUM(s.amount_sold) DESC) AS rank_roku
FROM sh.sales s
JOIN sh.times t ON s.time_id = t.time_id
GROUP BY t.calendar_year
ORDER BY rok;
Analiza Pareto (80/20) — koncentracja sprzedaży
-- Które produkty odpowiadają za 80% przychodów?
SELECT prod_id, sprzedaz, suma_biezaca, suma_total,
    ROUND(suma_biezaca / suma_total * 100, 2) AS procent_skumulowany
FROM (
    SELECT prod_id,
        SUM(amount_sold) AS sprzedaz,
        SUM(SUM(amount_sold)) OVER (ORDER BY SUM(amount_sold) DESC
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS suma_biezaca,
        SUM(SUM(amount_sold)) OVER () AS suma_total
    FROM sh.sales
    GROUP BY prod_id
)
WHERE suma_biezaca / suma_total <= 0.8   -- pierwsze 80% przychodów
ORDER BY sprzedaz DESC;
DENSE_RANK — VIP klienci + efektywność kanałów
-- VIP klienci (top 10% wg wydatków):
SELECT cust_id, suma_zakupow, dr FROM (
    SELECT cust_id,
        SUM(amount_sold) AS suma_zakupow,
        DENSE_RANK() OVER (ORDER BY SUM(amount_sold) DESC) AS dr
    FROM sh.sales
    GROUP BY cust_id
)
WHERE dr <= (SELECT COUNT(DISTINCT cust_id) * 0.1 FROM sh.sales);

-- Efektywność kanału: sprzedaż vs koszty:
SELECT s.channel_id,
    SUM(s.amount_sold) AS przychod,
    SUM(c.unit_cost * s.quantity_sold) AS koszt,
    SUM(s.amount_sold) - SUM(c.unit_cost * s.quantity_sold) AS zysk,
    RANK() OVER (ORDER BY SUM(s.amount_sold) -
        SUM(c.unit_cost * s.quantity_sold) DESC) AS rank_zysku
FROM sh.sales s
JOIN sh.costs c ON s.prod_id = c.prod_id AND s.time_id = c.time_id
                AND s.channel_id = c.channel_id
GROUP BY s.channel_id
ORDER BY rank_zysku;

Pytania egzaminacyjne

🔤
Pytanie ABCD

Dane: pensje [2000, 3000, 3000, 5000]. Jakie wartości zwróci RANK() ORDER BY salary?

🔤
Pytanie ABCD

Co zwróci LAST_VALUE(salary) OVER (PARTITION BY dept ORDER BY salary) bez dodatkowej klauzuli ROWS?

🔤
Pytanie ABCD

Która funkcja zwróci wynik bez luk w numeracji nawet przy remisach?

🔤
Pytanie ABCD

LAG(salary, 2, 0) OVER (ORDER BY hire_date) zwraca...

🔤
Pytanie ABCD

Czym różni się ROWS BETWEEN 3 PRECEDING AND CURRENT ROW od RANGE BETWEEN 3 PRECEDING AND CURRENT ROW?

🔍 Czy to się wykona?
SELECT department_id, salary,
    SUM(salary) OVER (PARTITION BY department_id ORDER BY salary
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS suma
FROM hr.employees;
🔍 Czy to się wykona?
SELECT first_name,
    RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rnk
FROM hr.employees
WHERE rnk <= 3;
🔍 Czy to się wykona?
SELECT RANK(8000) WITHIN GROUP (ORDER BY salary) AS pozycja
FROM hr.employees;
🔎 Znajdź błąd — wskaż niepoprawną linię
1SELECT first_name, salary,
2 RANK() OVER (ORDER BY salary DESC) AS rnk
3FROM hr.employees
4WHERE rnk <= 5;
🔎 Znajdź błąd — wskaż niepoprawną linię
1SELECT department_id, last_name, salary,
2 LAST_VALUE(salary) OVER (
3 PARTITION BY department_id
4 ORDER BY salary DESC
5 ) AS min_salary
6FROM hr.employees;
🔎 Znajdź błąd — wskaż niepoprawną linię
1SELECT department_id,
2 SUM(salary) AS suma,
3 RANK() OVER (ORDER BY SUM(salary) DESC) AS rank_dzialu
4FROM hr.employees
5GROUP BY department_id
6HAVING RANK() OVER (ORDER BY SUM(salary) DESC) <= 3;

Ćwiczenia

💪

Ćwiczenie 1 — TOP 3 wg pensji w każdym dziale

Wyświetl top 3 najlepiej zarabiających pracowników w każdym dziale. Użyj DENSE_RANK. Pokaż department_id, imię, nazwisko, pensję i rank.

💪

Ćwiczenie 2 — Suma bieżąca pensji wg daty zatrudnienia

Dla każdego pracownika wyświetl datę zatrudnienia, pensję oraz narastającą sumę pensji (w kolejności dat zatrudnienia). Dodaj też LAG — pensję poprzednio zatrudnionego pracownika.

💪

Ćwiczenie 3a — SH: ranking produktów per kanał

Z tabel sh.sales, sh.products i sh.channels wyświetl TOP 3 produktów (prod_name) wg sumy amount_sold w każdym kanale (channel_desc). Pokaż rank w kanale.

💪

Ćwiczenie 3b — SH: analiza Pareto (80% przychodów)

Które produkty odpowiadają za pierwsze 80% łącznych przychodów sh.sales? Pokaż prod_id, sprzedaż, sumę skumulowaną i procent skumulowany. Ogranicz do wierszy gdzie procent skumulowany <= 80%.

💪

Ćwiczenie 5 — Udział pensji i percentyle

Wyświetl pracowników z ich udziałem pensji w całości firmy (RATIO_TO_REPORT) oraz w którym kwartylu pensji się znajdują (NTILE(4)). Pokaż też PERCENT_RANK.