programowanieAnalityk SQL

Jak efektywnie realizować agregację z filtrowaniem warstwowym przy użyciu funkcji okiennych i grupowania w SQL? Jakie są różnice entre podejściami i jakie błędy często popełniają deweloperzy?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Aby efektywnie budować agregacje z filtrowaniem według "poziomów" (na przykład najpierw filtrując, a następnie obliczając sumy w grupach, a potem w całym zbiorze), używane są funkcje okienne (OVER()) i zagnieżdżone GROUP BY.

  • Grupowanie przez GROUP BY agreguje tylko według wybranych pól; wyeliminowanie wpływu "obcych" wierszy można osiągnąć tylko poprzez wstępny filtr (WHERE).
  • Funkcje okienne pozwalają obliczać agregaty dla określonych fragmentów danych, nakładając filtry na wynikowy zbiór (na przykład tylko wśród wierszy tej samej grupy).

Przykład: Znajdziemy maksymalną sumę zamówienia dla każdego menedżera, ale tylko wśród zamówień o statusie 'paid', a następnie wyprowadźmy imię menedżera z absolutnym maksimum wśród wszystkich.

WITH PaidOrders AS ( SELECT ManagerID, SUM(OrderAmount) AS TotalPaid FROM Orders WHERE Status = 'paid' GROUP BY ManagerID ), WithMax AS ( SELECT *, MAX(TotalPaid) OVER() AS MaxTotalPaid FROM PaidOrders ) SELECT ManagerID, TotalPaid FROM WithMax WHERE TotalPaid = MaxTotalPaid;

Takie podejście (CTE + funkcje okienne) pozwala na realizację wielopoziomowego filtrowania i agregacji.

Pytanie z pułapką.

Pułapka: "Czym różni się wykonanie filtru WHERE przed grupowaniem (GROUP BY) od zastosowania HAVING po? Jak to często przyczynia się do błędów w raportach?"

Odpowiedź: WHERE odrzuca wiersze jeszcze przed grupowaniem, co daje ścisły zestaw wejściowy. HAVING filtruje zagregowane grupy — dlatego może tymczasowo "zostawić" zbędne wiersze, jeśli filtr logicznie nie jest zgodny. Niewłaściwe miejsce filtra często prowadzi do błędów w końcowych agregatach lub nieprawidłowych wyników raportów.

-- Uzyskujemy sumę tylko dla 'paid', przez WHERE SELECT ManagerID, SUM(OrderAmount) FROM Orders WHERE Status = 'paid' GROUP BY ManagerID; -- Lub obliczamy sumy dla wszystkich, a potem tniemy HAVING SELECT ManagerID, SUM(OrderAmount) FROM Orders GROUP BY ManagerID HAVING SUM(OrderAmount) > 1000;

Przykłady rzeczywistych błędów wynikających z nieznajomości subtelności tematu.


Historia

Projekt: Raporty sprzedażowe, audyt weryfikacyjny.

Błąd: Deweloper zastosował HAVING Status='paid' zamiast WHERE, agregaty błędnie uwzględniały również nieopłacone zamówienia, przez co błędnie obliczano roczne KPI personelu.



Historia

Projekt: Analiza bankowa.

Błąd: Do skomplikowanej agregacji próbowano zastosować funkcję okienną bez PARTITION BY, przez co agregaty były obliczane na całej tabeli zamiast w grupie. Budżet departamentu został obliczony niepoprawnie — konieczne było ręczne przywracanie.



Historia

Projekt: Sklep internetowy, statystyki zamówień.

Błąd: Zagnieżdżenie funkcji okiennych w podzapytaniu nie zostało uwzględnione podczas optymalizacji — przez co serwer dokonywał wielokrotnej obróbki danych, a zapytanie działało 20 razy wolniej niż prosta operacja z podwójnym GROUP BY.