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 piszeszORDER BYbez 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_VALUEiLAST_VALUEzwracają pierwszą lub ostatnią wartość w ramce. PrzyLAST_VALUEuważaj na ramkę: domyślna ramka kończy się naCURRENT ROW, więc zwykle potrzebujeszROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING, żeby dostać faktyczną ostatnią wartość partycji.NTILE(n)dzieli wiersze nanmniej 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 BYredukuje. 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
WHEREnie 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 toRANGE UNBOUNDED PRECEDING. Jeśli chodziło ci o sumę całej partycji, napiszOVER (PARTITION BY ...)bezORDER BYalbo podaj ramkę jawnie. LAST_VALUEza 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.