Historia pytania:
Standardowy SQL został pierwotnie zaprojektowany jako język deklaratywny, bez elementów klasycznego programowania (pętli, rozgałęzień, bezpośrednich przejść), jednak z rozszerzeniami takimi jak T-SQL/PLSQL zaczęły być dostępne konstrukcje takie jak WHILE, CASE, a nawet imitacja GOTO.
Problem:
Operacje iteracyjne (pętla przetwarzania pojedynczych rekordów) często prowadzą do spowolnienia działania, szczególnie przy przetwarzaniu dużych wolumenów, jeśli nie zastąpi się ich podejściem "zbiorowym" (set-based). Rozgałęzienia, CASE, IF — są bardzo wygodne, ale przy nadmiernym zagnieżdżeniu cierpi czytelność i przewidywalność kodu.
Rozwiązanie:
Użycie konstrukcji sterujących powinno być uzasadnione tylko wtedy, gdy nie można obejść się bez przetwarzania zbiorowego (bulk/set)! Dla skomplikowanych obliczeń — dopuszczalna jest mała pętla, wyzwalacz lub CASE. Dla masowego przetwarzania — lepiej stosować funkcje okienne lub UPDATE z podzapytaniem.
Przykład kodu (T-SQL):
DECLARE @i INT = 1 WHILE (@i <= 5) BEGIN IF @i % 2 = 0 PRINT CONCAT('Parzyste: ', @i) ELSE PRINT CONCAT('Nieparzyste: ', @i) SET @i = @i + 1 END
Wyrażenie CASE:
SELECT num, CASE WHEN num % 2 = 0 THEN 'parzyste' ELSE 'nieparzyste' END AS parytet FROM numbers
Kluczowe cechy:
Czy CASE może być używane do filtrowania wierszy, jak WHERE?
Nie! CASE zwraca różne wartości, ale nie filtruje wierszy. Częstym błędem jest "filtrowanie" przez CASE zamiast WHERE, wynik będzie nieprawidłowy.
Czym różni się WHILE od CURSOR — czy to nie to samo?
WHILE — podstawowa pętla, z użytkownikowym zarządzaniem zmienną; CURSOR działa na rekordach tabeli, trzyma odniesienie do wiersza. CURSOR jest bardziej zasobożerny, często działa znacznie wolniej dla dużych danych.
Jakie podejście jest szybsze dla masowego przetwarzania: pętla WHILE z UPDATE czy jedno set-based UPDATE?
W 99% przypadków jedno zbiorowe UPDATE (lub INSERT) jest znacznie szybsze niż pętla przetwarzająca jeden rekord (nawet jeśli wydaje się, że jest to "bardziej elastyczne").
-- Nieprawidłowe podejście DECLARE @id INT = 1 WHILE (@id <= 100000) BEGIN UPDATE t SET flag=1 WHERE id=@id SET @id = @id + 1 END -- Prawidłowe UPDATE t SET flag=1 WHERE id BETWEEN 1 AND 100000
W projekcie do aktualizacji statusu dla miliona zamówień napisano pętlę z UPDATE dla każdego id. Przetwarzanie trwało ~8 godzin. Przy awarii w połowie — tracili wszystko, trzeba było naprawiać ręcznie.
Zalety:
Wady:
Przekształcono na set-based UPDATE z jednym wyrażeniem. Czas wykonania skrócił się do 6 minut, przetwarzanie jest atomowe.
Zalety:
Wady: