Rekursja w SQL i jej zastosowanie w controlling’u finansowym

Czym jest rekursja?

Rekursja to proces, w którym funkcja wywołuje samą siebie. W kontekście programowania oznacza to, że funkcja może odwoływać się do siebie wielokrotnie, aż osiągnie warunek zakończenia. Rekursja jest szczególnie użyteczna przy przetwarzaniu danych hierarchicznych, takich jak struktury organizacyjne, drzewa katalogów, plany kont czy zależności projektowe.

Dla osoby pracującej w controllingu finansowym rekursja może wydawać się bardziej pojęciem matematycznym niż praktycznym narzędziem. Jednak kiedy zrozumiemy, jak działa i jak może pomóc nam zautomatyzować analizę danych hierarchicznych, szybko przekonamy się o jej wartości.

Przykładem z życia codziennego może być analiza kosztów w firmie z wielopoziomową strukturą organizacyjną, gdzie koszty muszą być przypisane zarówno do jednostek niższego rzędu (np. działów), jak i zagregowane na poziomie całej dywizji czy firmy.

Jak działa rekursja w kontekście SQL?

W SQL rekursja możliwa jest dzięki zapytaniom CTE (Common Table Expression) z użyciem słowa kluczowego WITH RECURSIVE. To specjalny rodzaj zapytania, który pozwala na wielokrotne przetwarzanie danych w pętli, aż zostaną spełnione określone warunki. Składnia może wyglądać nieco skomplikowanie, ale logika jest stosunkowo prosta; rozpoczynamy od tzw. zapytania podstawowego (anchor member), a następnie definiujemy część rekurencyjną, która odnosi się do poprzednich wyników.

Zastosowanie WITH RECURSIVE może być porównane do wspinaczki po drabinie; zaczynasz od najniższego szczebla (np. najniższego poziomu hierarchii), a zapytanie przemieszcza się w górę lub w dół, aż dotrze do szczytu (lub dna) struktury.

Dla Kontrolera Finansowego oznacza to np. możliwość prześledzenia przepływu kosztów od konkretnego projektu przez działy, aż do centrali firmy i to bez konieczności ręcznego łączenia tabel czy tworzenia setek JOINów.

WITH RECURSIVE – składnia i logika działania

Podstawowym narzędziem umożliwiającym rekursję w SQL jest konstrukcja WITH RECURSIVE. To rozszerzenie standardowego CTE (Common Table Expression), które umożliwia stworzenie zapytania odwołującego się do samego siebie. Składnia wygląda następująco:

sql
WITH RECURSIVE nazwa_cte (kolumny...) AS (
-- Część podstawowa (anchor member)
SELECT ...
FROM ...
WHERE warunki_początkowe

UNION ALL

-- Część rekurencyjna (recursive member)
SELECT ...
FROM nazwa_cte
JOIN ...
ON warunki_połączenia

)
SELECT * FROM nazwa_cte;

Pierwsza część to tzw. anchor, czyli punkt startowy zapytania. To od niego zaczynamy budować wyniki. Druga część rekurencyjna odnosi się do wyników uzyskanych w poprzednim kroku i rozszerza je dalej, aż do spełnienia określonego warunku zakończenia.

W praktyce oznacza to, że możemy zbudować np. pełne drzewo struktury organizacyjnej firmy, hierarchię kont księgowych, zależności projektowe czy relacje między centrami kosztów – wszystko w jednym zapytaniu SQL.

Część podstawowa i rekurencyjna zapytania

Dobrze zaprojektowane zapytanie rekurencyjne powinno być podzielone na dwie wyraźne sekcje:

  1. Część podstawowa (anchor): Tutaj wybieramy dane początkowe. Na przykład: w strukturze organizacyjnej może to być CEO firmy lub osoba, która nie ma przełożonego.
  2. Część rekurencyjna: Łączy się z wynikami z poprzednich kroków. Tutaj budujemy relacje między poziomami np. przełożony → podwładny lub konto nadrzędne → konto podrzędne.

Kluczem do poprawności zapytania jest zapewnienie warunku zakończenia. W przeciwnym razie zapytanie może zakończyć się błędem pętli nieskończonej. Warto również znać mechanizmy, takie jak MAXRECURSION w SQL Server lub LIMIT w PostgreSQL, które zabezpieczają przed takimi sytuacjami.

Dla Kontrolera to oznacza jedno: możemy automatyzować konsolidację danych w hierarchiach bez pisania wielu skomplikowanych zapytań. Jeden dobrze napisany CTE rekurencyjny wystarczy, aby stworzyć pełen obraz kosztów, przychodów lub innych metryk.

Przykład: struktura organizacyjna firmy

Weźmy na warsztat strukturę organizacyjną firmy: klasyczny przypadek danych hierarchicznych. Załóżmy, że mamy tabelę employees z kolumnami: employee_id, employee_name i manager_id. Chcemy uzyskać drzewo organizacyjne firmy; czyli kto komu podlega.

Dzięki rekursji możemy zacząć od pracownika najwyższego szczebla (np. CEO) i w każdym kroku dołączać podwładnych, aż do najniższych poziomów.

WITH RECURSIVE org_chart AS (
SELECT employee_id, employee_name, manager_id, 1 AS level
FROM employees
WHERE manager_id IS NULL

UNION ALL

SELECT e.employee_id, e.employee_name, e.manager_id, oc.level + 1
FROM employees e
JOIN org_chart oc ON e.manager_id = oc.employee_id
)
SELECT * FROM org_chart;

Ten prosty kod tworzy pełne drzewo organizacyjne. Każdy poziom struktury (dyrektor, kierownik, pracownik) jest oznaczony poziomem level, co dodatkowo ułatwia analizę.

Wyszukiwanie przełożonych i podwładnych

Dzięki rekursji możemy nie tylko zbudować całą strukturę organizacyjną, ale także odpowiedzieć na konkretne pytania:

  • Kto jest bezpośrednim przełożonym danego pracownika?
  • Jacy pracownicy znajdują się pod konkretnym menedżerem?
  • Jak wygląda cała ścieżka podległości od pracownika do CEO?

Tego typu analizy są niezwykle cenne w controllingu personalnym, w zarządzaniu wydajnością, analizie wynagrodzeń, a także w planowaniu kosztów zespołów czy projektów.

Hierarchia kont księgowych (plan kont)

Jednym z najbardziej oczywistych zastosowań rekursji w controllingu finansowym jest analiza planu kont. W wielu systemach ERP konta księgowe zorganizowane są w strukturze drzewa: konto główne → podkonto → subkonto itd.

Załóżmy, że mamy tabelę accounts z kolumnami account_id, account_name, parent_account_id. Dzięki rekursji jesteśmy w stanie:

  • Przedstawić pełną hierarchię kont,
  • Obliczyć wartości agregowane dla kont nadrzędnych na podstawie podkont,
  • Szybko przeanalizować dane z poziomu ogólnego do szczegółowego.
WITH RECURSIVE account_tree AS (
SELECT account_id, account_name, parent_account_id, 0 AS depth
FROM accounts
WHERE parent_account_id IS NULL

UNION ALL

SELECT a.account_id, a.account_name, a.parent_account_id, at.depth + 1
FROM accounts a
JOIN account_tree at ON a.parent_account_id = at.account_id
)
SELECT * FROM account_tree;

Dzięki tej strukturze możemy w jednym zapytaniu uzyskać pełną mapę kont księgowych, a także po połączeniu z wartościami księgowymi; stworzyć analizę konsolidacyjną.

Agregacja danych finansowych z poziomu podkont do kont głównych

Po zbudowaniu drzewa kont, naturalnym kolejnym krokiem jest agregacja wartości finansowych. Konta nadrzędne (np. „Koszty operacyjne”) mogą zbierać dane z dziesiątek kont podrzędnych. W SQL z rekursją i funkcjami agregującymi (SUM, AVG, COUNT) możemy to zrobić błyskawicznie.

SELECT at.parent_account_id, SUM(f.value) AS total_value
FROM account_tree at
JOIN finance_data f ON f.account_id = at.account_id
GROUP BY at.parent_account_id;

Tego typu konsolidacja to codzienność w controllingu. Rekursja pozwala zrobić to szybko, dokładnie i automatycznie.

Dane wejściowe: plan kont i wartości księgowe

Wyobraźmy sobie, że pracujemy w dziale controllingu dużej firmy produkcyjnej. Posiadamy dane finansowe w dwóch tabelach:

  1. Tabela kont (accounts)
    • account_id: unikalny identyfikator konta,
    • account_name: nazwa konta,
    • parent_account_id: wskazanie konta nadrzędnego (NULL dla kont głównych).
  2. Tabela danych finansowych (financial_data)
    • account_id: identyfikator konta, którego dotyczy wartość,
    • value: kwota przypisana do konta,
    • period: okres sprawozdawczy.

Struktura tych danych jest typowa dla systemów finansowo-księgowych (np. SAP, Oracle Financials), w których plan kont opiera się na hierarchiach. Celem controllera jest szybkie stworzenie zbiorczego raportu, który pokazuje wartości nie tylko na poziomie poszczególnych kont, ale także na poziomach zagregowanych (sumy kont głównych, grup, działów itd.).Zapytanie rekurencyjne konsolidujące dane

Za pomocą zapytania rekurencyjnego możemy zbudować hierarchię kont i jednocześnie połączyć ją z tabelą finansową. W efekcie otrzymamy strukturę drzewa, na której możemy wykonać agregację danych.

WITH RECURSIVE account_hierarchy AS (
SELECT account_id, account_name, parent_account_id, account_id AS root_id
FROM accounts
WHERE parent_account_id IS NULL
UNION ALL
SELECT a.account_id, a.account_name, a.parent_account_id, ah.root_id
FROM accounts a
JOIN account_hierarchy ah ON a.parent_account_id = ah.account_id
)
SELECT ah.root_id AS parent_account,
SUM(fd.value) AS total_value
FROM account_hierarchy ah
JOIN financial_data fd ON ah.account_id = fd.account_id
WHERE fd.period = '2025-06'
GROUP BY ah.root_id;

To zapytanie wykonuje potężną operację: tworzy całą strukturę planu kont i sumuje wartości z każdego poziomu w strukturze w górę do kont głównych (root_id). Jest to idealne rozwiązanie dla Kontrolera, który potrzebuje zobaczyć łączną wartość kosztów lub przychodów na poziomie całych działów, regionów lub grup kosztów.

Analiza wyników i korzyści dla controllingu

Rezultat? Czysty, zwięzły i uporządkowany raport, który:

  • Pokazuje wartości skonsolidowane dla każdego konta nadrzędnego,
  • Zachowuje pełną przejrzystość hierarchii kont,
  • Może być łatwo rozszerzony o dodatkowe wymiary (np. jednostki organizacyjne, projekty, centra kosztów).

Dla controllera to narzędzie idealne – nie tylko automatyzuje skomplikowaną analizę, ale także pozwala wyeliminować błędy ludzkie typowe dla ręcznej konsolidacji danych (np. w Excelu). Co więcej, można je zintegrować z narzędziami raportowymi (Power BI, Tableau) lub systemami planowania (SAP BPC, Oracle Hyperion), tworząc kompletne środowisko BI.

Powiązania między centrami kosztów

W większych organizacjach centra kosztów często posiadają zależności hierarchiczne: np. dział IT podlega pod dział administracyjny, który z kolei podlega całej jednostce korporacyjnej. Każdy z tych poziomów ma swój budżet i raporty, ale controlling musi umieć patrzeć na koszty z każdej perspektywy – od najbardziej ogólnej do najbardziej szczegółowej.

Za pomocą rekursji możemy odwzorować powiązania między centrami kosztów, które znajdują się w jednej tabeli cost_centers:

  • center_id: identyfikator centrum,
  • center_name: nazwa centrum,
  • parent_center_id: identyfikator nadrzędnego centrum.

Dzięki strukturze podobnej do drzewa, możemy stworzyć raporty przedstawiające skumulowane koszty dla każdej jednostki i jej podjednostek.

Śledzenie kosztów po strukturze hierarchicznej

Zastosujmy rekursję, aby zsumować koszty z podrzędnych centrów:

KopiujEdytujWITH RECURSIVE cost_tree AS (
SELECT center_id, parent_center_id, center_id AS root_center
FROM cost_centers
WHERE parent_center_id IS NULL

UNION ALL

SELECT c.center_id, c.parent_center_id, ct.root_center
FROM cost_centers c
JOIN cost_tree ct ON c.parent_center_id = ct.center_id
)

SELECT ct.root_center, SUM(cd.cost_value) AS total_cost
FROM cost_tree ct
JOIN cost_data cd ON cd.center_id = ct.center_id
WHERE cd.period = '2025-Q2'
GROUP BY ct.root_center;

Efektem jest dynamiczny raport kosztów pokazujący:

  • Koszty zagregowane na poziomie jednostek nadrzędnych,
  • Możliwość porównywania budżetów i wykonania,
  • Szybką lokalizację odchyleń w strukturze kosztowej.

Takie podejście daje controllerowi pełen obraz organizacyjny i pozwala na szybsze reagowanie na nieprawidłowości finansowe.

Ryzyko zapętlenia i limit głębokości

Choć rekursja w SQL jest potężnym narzędziem, niesie też ze sobą pewne zagrożenia. Jednym z najczęstszych problemów jest zapętlenie – sytuacja, w której dane wejściowe zawierają błędne odniesienia i zapytanie nigdy się nie kończy. Może się to zdarzyć np. wtedy, gdy konto księgowe A wskazuje jako nadrzędne konto B, a B z kolei A – w efekcie tworząc cykl.

Aby tego uniknąć, systemy baz danych stosują ograniczenia:

  • MAXRECURSION (SQL Server): Domyślnie ustawia limit 100 kroków rekurencji. Można go zwiększyć (np. OPTION (MAXRECURSION 1000)), ale warto to robić ostrożnie.
  • LIMIT (PostgreSQL): W PostgreSQL możemy ograniczyć liczbę poziomów wyników zapytania.

Dobrą praktyką jest też zabezpieczenie danych wejściowych na przykład: zanim uruchomimy zapytanie, warto sprawdzić spójność relacji rodzic: dziecko i brak cyklicznych zależności. W systemach klasy ERP często implementuje się walidację już na poziomie interfejsu użytkownika lub podczas importu danych.

Wydajność przy dużych zbiorach danych

Innym istotnym aspektem są problemy wydajnościowe. W przypadku danych o dużej głębokości (np. 10+ poziomów hierarchii) lub ogromnych wolumenów (setki tysięcy rekordów), zapytania rekurencyjne mogą znacznie obciążać bazę danych.

Kilka sposobów optymalizacji:

  • Użycie indeksów na kolumnach parent_id i id jest to kluczowe przy łączeniu danych.
  • Ograniczanie wyników przez WHERE w części podstawowej zapytania.
  • Cache wyników rekurencyjnych do tabel tymczasowych lub materializowanych widoków.
  • Rozdzielenie analizy na mniejsze fragmenty i wykorzystanie ETL lub procedur składowanych.

W controllingu, gdzie dane są przetwarzane cyklicznie, często stosuje się podejście batchowe – tzn. dane są rekurencyjnie agregowane raz dziennie, a wyniki zapisywane do tabel raportowych. To kompromis między wydajnością a elastycznością.

CTE nierekurencyjne z wieloma JOIN

Jeśli nasza hierarchia nie przekracza 3–4 poziomów, a struktura danych jest stabilna, można zbudować strukturę bez rekursji – za pomocą klasycznych JOIN:

KopiujEdytujSELECT a.account_name AS level1, b.account_name AS level2, c.account_name AS level3
FROM accounts a
LEFT JOIN accounts b ON b.parent_account_id = a.account_id
LEFT JOIN accounts c ON c.parent_account_id = b.account_id;

Ten sposób jest bardziej czytelny i często szybszy, ale ma ograniczoną elastyczność. Zmiana głębokości hierarchii wymaga zmiany zapytania – co w dynamicznych strukturach, typowych dla controllingu, jest niepraktyczne.

Procedury składowane i pętle w PL/SQL lub T-SQL

Dla bardziej zaawansowanych zastosowań można używać proceduralnych języków SQL, takich jak:

  • PL/pgSQL (PostgreSQL),
  • T-SQL (SQL Server),
  • PL/SQL (Oracle).

Pozwalają one tworzyć pętle i struktury kontrolne (np. IF, WHILE), które imitują rekursję. Przykładowo, zamiast CTE można zbudować funkcję, która rekurencyjnie przechodzi przez strukturę kont lub centrów kosztów i zapisuje wyniki do tabeli tymczasowej.

To podejście daje pełną kontrolę nad logiką przetwarzania i umożliwia stosowanie zaawansowanej logiki biznesowej. Jest też często bardziej wydajne przy bardzo dużych strukturach. Wadą jest większa złożoność i zależność od silnika bazy danych.

Optymalizacja zapytań rekurencyjnych

Aby rekursja w SQL była skuteczna i szybka, warto stosować kilka zasad:

  • Filtrowanie już na początku: Im mniej danych przetwarza część rekurencyjna, tym szybciej działa zapytanie.
  • Stosowanie indeksów: Indeksy na id i parent_id to podstawa.
  • Limitowanie głębokości rekursji: Użycie LIMIT lub MAXRECURSION, by uniknąć niekończących się zapytań.
  • Stosowanie aliasów i warunków logicznych: Jasne i przejrzyste warunki JOIN ułatwiają debugowanie.
  • Testowanie i wersjonowanie zapytań: Zmiany w hierarchiach mogą mieć nieprzewidywalne skutki – warto utrzymywać wersje testowe i produkcyjne.

Przykłady z życia codziennego controllera

W codziennej pracy controllera można znaleźć wiele scenariuszy, w których rekursja oszczędza czas:

  • Budżetowanie i raportowanie kosztów jednostek złożonych z wielu podjednostek,
  • Konsolidacja przychodów i kosztów dla grupy firm,
  • Tworzenie struktur zatwierdzania kosztów na wielu poziomach (workflow),
  • Tworzenie wielopoziomowych analiz ROI w projektach inwestycyjnych.

Rekursja to nie tylko sztuczka dla programistów – to realne narzędzie, które może znacząco ułatwić życie controllerowi i zwiększyć jakość analizy danych.

Automatyzacja i raportowanie BI

W dobie automatyzacji i zaawansowanej analityki, rekursja w SQL znajduje swoje miejsce w systemach Business Intelligence. Narzędzia takie jak Power BI, Tableau czy Looker coraz częściej wspierają natywne zapytania SQL – co pozwala na dynamiczne budowanie raportów o strukturze hierarchicznej.

Przykładowo:

  • Drzewa organizacyjne z dynamicznymi rozwijanymi poziomami,
  • Konsolidowane wykresy kosztów z podziałem na działy i projekty,
  • Dashboardy menedżerskie z podsumowaniem danych finansowych.

Integracja z narzędziami BI umożliwia kontrolerom nie tylko analizę, ale i wizualizację danych, co zwiększa wartość raportów w oczach zarządu.

Integracja z narzędziami analitycznymi

Przyszłość controllingu to integracja danych – i rekursja może tu odegrać istotną rolę. Dzięki możliwościom SQL i rekurencji:

  • Można tworzyć wielowymiarowe modele danych (konta, centra kosztów, jednostki organizacyjne),
  • Automatyzować budowę wskaźników KPI,
  • Tworzyć symulacje finansowe, w których zmiana wartości w jednym węźle drzewa wpływa na wyniki całej organizacji.

To właśnie rekursja jako sposób myślenia i narzędzie techniczne pozwala modelować rzeczywistość finansową w sposób bliski temu, jak działa organizacja.

Rekursja w SQL to nie tylko ciekawostka programistyczna, ale potężne narzędzie analityczne, które znajduje realne zastosowanie w controllingu finansowym. Od analizy planów kont, przez konsolidację kosztów, aż po raportowanie struktury organizacyjnej – wszędzie tam, gdzie mamy do czynienia z danymi hierarchicznymi, rekursja daje przewagę.

Umiejętność korzystania z WITH RECURSIVE, znajomość struktury danych i dobre praktyki optymalizacyjne pozwalają controllerom tworzyć raporty szybciej, dokładniej i bardziej przejrzyście. W erze danych, złożonych struktur i potrzeby błyskawicznego raportowania, rekursja staje się nie tylko opcją – ale koniecznością.

1. Czy wszystkie systemy baz danych wspierają zapytania rekurencyjne?
Nie. WITH RECURSIVE działa m.in. w PostgreSQL, MySQL 8+, Oracle, DB2 i SQL Server (choć składnia może się różnić). W starszych wersjach MySQL lub SQLite konieczne może być użycie alternatyw.

2. Jakie dane najlepiej analizować z pomocą rekursji w SQL?
Dane hierarchiczne: plan kont, struktura organizacyjna, centra kosztów, produkty z wariantami, struktury projektów.

3. Czy rekursja jest wydajna w dużych zbiorach danych?
Może być, ale wymaga optymalizacji: indeksów, filtrowania, limitów rekursji i ewentualnego batchowania wyników.

4. Czym różni się CTE rekurencyjny od zwykłego JOIN-a?
JOIN łączy dane poziomo; CTE rekurencyjny pozwala łączyć je wielopoziomowo – aż do wyczerpania struktury.

5. Czy warto uczyć się rekursji w SQL jako controller?
Zdecydowanie tak. To umiejętność, która zwiększa Twoje możliwości analityczne i czyni Twoje raporty bardziej dynamicznymi i precyzyjnymi.