programowanieProgramista Backend

Jak zaimplementować obliczanie kumulowanych (narastających) wyników w SQL bez funkcji okiennych, z uwzględnieniem wydajności dla tysięcy lub milionów wierszy?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Obliczanie kumulowanych wyników i sum bieżących tradycyjnie w SQL było realizowane poprzez funkcje okienne (na przykład, SUM() OVER(ORDER BY ...)), jednak w wczesnych lub uproszczonych wersjach DBMS dostępne są jedynie podzapytania i grupowania. Historycznie architekci baz danych poszukiwali obejść do momentu pojawienia się standardu SQL:2003 z obsługą funkcji okiennych.

Problem — przy braku funkcji okiennych dla każdego wiersza konieczne jest jawne obliczenie sumy wszystkich poprzednich wartości, co prowadzi do złożoności O(N^2) w przypadku dużych zbiorów danych, jeśli nie zastosuje się sprytnych rozwiązań.

Rozwiązanie:

Zazwyczaj używa się skorelowanych podzapytań lub tymczasowych tabel z aktualizacją wartości:

Przykład kodu:

-- Kumulowana suma przez skorelowane zapytanie SELECT t1.id, t1.amount, ( SELECT SUM(t2.amount) FROM transactions t2 WHERE t2.id <= t1.id ) AS running_total FROM transactions t1 ORDER BY t1.id; -- Przez tymczasową tabelę z ręcznym aktualizowaniem wartości CREATE TEMPORARY TABLE temp_running (id INT, amount INT, running_total INT); -- Przechodzimy przez wiersze używając zewnętrznego kodu (na przykład, pl/pgsql), kolejno dodając sumę

Kluczowe cechy:

  • Metoda działa tylko wtedy, gdy istnieje unikalny kryterium sortowania (id, data utworzenia)
  • Skorelowane podzapytanie ma słabą skalowalność — wykładniczy wzrost czasu wykonania
  • Dla dużych zbiorów danych logiczniejszym rozwiązaniem jest użycie ETL z agregacją poza SQL lub przy użyciu procedur

Pytania z podstępem.

Czy gwarantuje posortowanie ORDER BY w skorelowanym podzapytaniu?

Nie — zapytanie podrzędne niekoniecznie wpływa na sam wynik. Sortowanie końcowego zbioru jest zawsze określane zewnętrznie w głównym zapytaniu: wynik zależy tylko od filtracji według WHERE.

Czy można równolegle obliczać kumulowaną sumę w tym podejściu?

Nie — kolejność jest bardzo ważna, szczególnie przy obliczeniach w zależności od poprzednich wierszy, przez co proste równoleglenie jest niemożliwe w zwykłym SQL.

Dlaczego skorelowane podzapytanie jest tak wolne przy dużej liczbie wierszy?

Ponieważ dla każdego wiersza na nowo obliczana jest suma dla zestawu poprzednich wierszy. To prowadzi do O(N^2) operacji. Na próbce 100 tys. wierszy to już może zająć minuty, a nawet godziny.

Typowe błędy i antywzorce

  • Nieprawidłowe filtrowanie po id zamiast faktycznej daty — sumy "skaczą" na dziury w id
  • Próba wykonywania sumowania bez uporządkowania danych
  • Używanie takiego podejścia dla ogromnych tabel, kiedy potrzebny jest ETL lub partycjonowane przetwarzanie

Przykład z życia

Negatywny przypadek

Analityk obliczył dzienną kumulowaną przychód według daty przez skorelowane podzapytanie, a w tabeli okresowo pojawiały się usunięte id (dziury). Końcowa suma miała skokowe spadki i zależała nie od daty, ale od porządku id.

Plusy:

  • Działa dla małych zbiorów, nie wymaga funkcji okiennych

Minusy:

  • Dane są niepoprawne, oblicza nie tak, jak oczekiwano
  • Trudna konserwacja

Pozytywny przypadek

Inżynier przeniósł przetwarzanie kumulowanej sumy do skryptu ETL (Python/pandas), a następnie załadował końcowe wartości do osobnej tabeli, synchronizując tylko nowości. Wyniki zawsze są zgodne z datą, kod działa szybko i z milionami rekordów.

Plusy:

  • Niezawodność, możliwość dalszego obliczania bez przerwy w działaniu
  • Wsparcie dla dużych zbiorów

Minusy:

  • Bardziej skomplikowany krajobraz — potrzebne zewnętrzne narzędzia przetwarzania