Menu

Funkcje okna w SQLite: OVER, PARTITION BY i ramki

Jak działają funkcje okna (window functions) w SQLite: OVER, PARTITION BY, funkcje rankingowe, LAG/LEAD i ramki do sum narastających.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Funkcja okna dodaje kolumnę, nie scalając wierszy

GROUP BY zamienia wiele wierszy w jeden. Funkcja okna robi coś innego: oblicza wartość na zbiorze powiązanych wierszy, ale zachowuje każdy wiersz wejściowy w wyniku. Dostajesz szczegóły wiersz po wierszu i agregat obok siebie.

Schemat jest zawsze taki sam: funkcja, a potem OVER (...).

Kolumna total_all pokazuje sumę całkowitą wszystkich wierszy, powtórzoną w każdej linii. Oryginalne wiersze pozostają nietknięte. Porównaj to z SELECT SUM(amount) FROM sales: ta sama liczba, ale wraca tylko jeden wiersz. Funkcje okna dają oba widoki naraz.

PARTITION BY: agregacja w grupach

Puste OVER () agreguje po całej tabeli. Dodaj PARTITION BY, żeby agregować w grupach, podobnie jak przy GROUP BY, ale znowu bez scalania wierszy.

Każdy wiersz dostaje sumę swojego regionu i swój udział w tej sumie. Przy zwykłym GROUP BY szczegóły dla poszczególnych pracowników by zniknęły. To główna zaleta funkcji okna: szczegóły i agregat w jednym zapytaniu.

Ranking: ROW_NUMBER, RANK, DENSE_RANK

Rodzina funkcji rankingowych numeruje wiersze według ORDER BY wewnątrz OVER. Trzy warianty różnią się obsługą remisów.

Jak czytać wynik:

  • ROW_NUMBER() jest zawsze unikalny: remisy są rozstrzygane dowolnie. Używaj go, gdy potrzebujesz stabilnego, odrębnego numeru dla każdego wiersza.
  • RANK() daje remisującym wierszom tę samą pozycję, a potem pomija kolejne numery. Po dwóch graczach remisujących na pozycji 1 następuje pozycja 3.
  • DENSE_RANK() też uwzględnia remisy, ale niczego nie pomija. Następna pozycja to 2.

Aby uzyskać „top N w każdej grupie”, połącz ranking z PARTITION BY i filtruj w zapytaniu zewnętrznym: WHERE nie może bezpośrednio odwoływać się do funkcji okna:

Dwie osoby z najwyższą sprzedażą w każdym regionie.

LAG i LEAD: zajrzyj do sąsiednich wierszy

LAG(col) zwraca wartość col z poprzedniego wiersza w oknie. LEAD(col) sięga do przodu. Obie idealnie nadają się do pytań o zmiany w czasie.

W pierwszym wierszu yesterday to NULL: przed nim nic nie ma. Możesz podać wartość domyślną: LAG(celsius, 1, celsius) OVER (ORDER BY day) użyje dzisiejszej wartości, gdy poprzedni wiersz nie istnieje.

LEAD to lustrzane odbicie. Połącz obie funkcje z PARTITION BY, żeby dostać sekwencje w grupach, na przykład porównać sprzedaż z tego miesiąca z poprzednim miesiącem w każdym regionie.

Sumy narastające z ramkami okna

Dodaj ORDER BY wewnątrz OVER, a funkcje agregujące takie jak SUM, AVG, COUNT zaczną liczyć narastająco:

Dwie rzeczy, na które warto zwrócić uwagę:

  • SUM(amount) OVER (ORDER BY day) to suma narastająca. Domyślna ramka, gdy piszesz ORDER BY bez jawnej ramki, to „od początku okna do bieżącego wiersza”.
  • Druga kolumna używa jawnej ramki: ROWS BETWEEN 2 PRECEDING AND CURRENT ROW. To przesuwane okno z trzech wierszy, czyli średnia krocząca.

Model myślowy ramek: każda funkcja okna jest obliczana na ramce wierszy zdefiniowanej względem bieżącego wiersza. Typowe ramki:

  • ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: suma narastająca (domyślna, gdy ramka nie jest podana).
  • ROWS BETWEEN N PRECEDING AND CURRENT ROW: okno kroczące wstecz.
  • ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING: cała partycja.

ROWS liczy fizyczne wiersze. Jest też RANGE, które grupuje według wartości: przydaje się, gdy w kolumnie ORDER BY są remisy i chcesz, żeby były traktowane jako jeden krok.

FIRST_VALUE, LAST_VALUE, NTILE

Kilka innych funkcji okna, które warto znać:

  • FIRST_VALUE i LAST_VALUE zwracają pierwszą lub ostatnią wartość w ramce. Przy LAST_VALUE uważaj na ramkę: domyślna ramka kończy się na CURRENT ROW, więc zwykle potrzebujesz ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING, żeby dostać faktyczną ostatnią wartość partycji.
  • NTILE(n) dzieli wiersze na n mniej więcej równych koszyków: przydaje się do kwartyli, percentyli i podziałów w stylu testów A/B.

Nazywanie okna przez WINDOW

Gdy kilka kolumn ma tę samą klauzulę OVER (...), powtarzanie jej staje się męczące. SQLite pozwala nazwać okno raz i używać go wielokrotnie:

To samo zapytanie, mniej szumu. Klauzula WINDOW stoi po WHERE/GROUP BY/HAVING, a przed ORDER BY.

Funkcje okna vs GROUP BY

Oba mechanizmy dotyczą agregacji, ale odpowiadają na różne pytania:

  • GROUP BY redukuje. Jeden wiersz na grupę. Używaj go, gdy chcesz tylko podsumowania.
  • Funkcje okna zachowują. Każdy wiersz wejściowy przetrwa, a obok pojawiają się dodatkowe obliczone kolumny.

Jeśli kiedyś robisz GROUP BY, a potem łączysz agregaty z powrotem z oryginalną tabelą, to mocny sygnał, że funkcja okna załatwiłaby sprawę jednym zapytaniem.

Kilka pułapek

  • WHERE nie może odwoływać się do funkcji okna. Filtry działają, zanim okna zostaną obliczone. Opakuj zapytanie w podzapytanie albo CTE i filtruj na poziomie zewnętrznym.
  • Niejawne ramki potrafią zaskoczyć. SUM(x) OVER (ORDER BY y) to suma narastająca, bo domyślna ramka to RANGE UNBOUNDED PRECEDING. Jeśli chodziło ci o sumę całej partycji, napisz OVER (PARTITION BY ...) bez ORDER BY albo podaj ramkę jawnie.
  • LAST_VALUE za pierwszym razem zaskakuje każdego. Przy domyślnej ramce kończącej się na bieżącym wierszu zwraca bieżącą wartość, a nie ostatnią wartość partycji. Nadpisz ramkę.
  • Funkcje okna wymagają SQLite 3.25+ (wydanego w 2018 roku). Każda w miarę nowa instalacja je ma, ale niektóre środowiska wbudowane są w tyle.

Dalej: kolumny generowane

Funkcje okna to obliczenia w momencie zapytania. Następna strona omawia obliczenia w momencie zapisu: kolumny generowane, których wartość jest zdefiniowana wyrażeniem i aktualizowana automatycznie, gdy zmieniają się dane źródłowe.

Najczęściej zadawane pytania

Czym są funkcje okna w SQLite?

Funkcje okna obliczają wartość na zbiorze wierszy powiązanych z bieżącym wierszem, nie scalając ich tak jak GROUP BY. Do funkcji takich jak ROW_NUMBER(), RANK(), SUM() czy LAG() dołączasz klauzulę OVER (...), która definiuje okno. Każdy wiersz wejściowy zostaje w wyniku: dostajesz tylko dodatkową obliczoną kolumnę.

Czym różni się RANK od DENSE_RANK w SQLite?

Obie funkcje nadają pozycję na podstawie ORDER BY, ale inaczej traktują remisy. RANK() zostawia luki po remisach: po dwóch wierszach z pozycją 1 następuje pozycja 3. DENSE_RANK() nie zostawia luk: następny wiersz dostaje pozycję 2. Użyj DENSE_RANK(), gdy chcesz kolejnych pozycji, a RANK(), gdy luka ma znaczenie.

Jak obliczyć sumę narastającą w SQLite?

Użyj SUM(column) OVER (ORDER BY ...) z ramką okna. Domyślnie ORDER BY wewnątrz OVER używa ramki RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, co daje sumę narastającą. Dodaj PARTITION BY, żeby suma zaczynała się od nowa w każdej grupie.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ