Praca z drzewami i hierarchiami to częsty przypadek przy programowaniu w SQL: katalogi produktów, struktura organizacyjna, menu. Początkowo większość systemów zarządzania bazami danych (SGBD) obsługiwała tylko odniesienie do rodzica (ParentId), ale z biegiem czasu pojawiły się alternatywne metody — zagnieżdżone zestawy i ścieżki (Path), a także zapytania rekurencyjne przez CTE.
Problem tradycyjnego podejścia Parent-Child to niska prędkość przy przechodzeniu przez duże hierarchie; zapytania na poziomie głębszym niż 2-3 prowadzą do lawinowego wzrostu liczby JOIN. Bardziej złożone techniki (Nested Sets, Path Enumeration) przyspieszają masowe przeszukiwania, ale utrudniają wstawianie/usuwanie.
Rozwiązanie — wybór optymalnego modelu przechowywania do konkretnego zadania:
Przykład rekursywnego wyszukiwania pod-drzewa przez CTE:
WITH RecursiveTree AS ( SELECT id, parent_id, name, 0 as level FROM categories WHERE parent_id IS NULL UNION ALL SELECT c.id, c.parent_id, c.name, rt.level + 1 FROM categories c INNER JOIN RecursiveTree rt ON c.parent_id = rt.id ) SELECT * FROM RecursiveTree;
Kluczowe cechy:
Czy można zastąpić przechowywanie drzew jedną denormalizowaną tabelą z jednym poziomem odniesień, jeśli głębokość jest stała?
Tylko jeśli głębokość jest mała i zawsze stała (2-3 poziomy) — w przeciwnym razie JOIN'y stają się niezarządzalne, a przetwarzanie zmian się komplikuje.
A CTE nie "zawiesi się" przy dużych drzewach — czy nie ma ograniczeń dotyczących stosu, głębokości rekurencji?
Tak, w większości SGBD ustalone są limity głębokości rekurencji (na przykład 100/32767). Dla drzew o głębokości 1000+ wymagana będzie ręczna kontrola głębokości lub dostosowane algorytmy przeszukiwania/podziału.
Nested Sets — rozwiązanie wszystkich problemów?
Nie, nested sets natychmiast wyszukują wszystkich potomków, ale wstawianie/usuwanie wymaga masowej aktualizacji wszystkich indeksów lewego/prawego — jest to wolne przy dużej liczbie częstych zmian.
Przykład wstawiania (uproszczony):
UPDATE tree SET lft = lft + 2 WHERE lft > @parent_right; UPDATE tree SET rgt = rgt + 2 WHERE rgt >= @parent_right; INSERT INTO tree(id, name, lft, rgt) VALUES(@new_id, @name, @parent_right, @parent_right + 1);
Duży sklep internetowy przechowywał drzewo kategorii w metodzie adjacency list i budował menu za pomocą zagnieżdżonych podzapytania. Przy 6+ poziomach menu główne zapytanie trwało kilka minut, a jakiekolwiek zmiany w drzewie prowadziły do cascade update. Zalety:
Wady:
Przeszliśmy na Nested Sets dla statycznych drzew (kategorii), a dla ścieżek w menu — na Path. Użyliśmy CTE dla dynamicznych scenariuszy. Wyszukiwanie potomków stało się natychmiastowe, a zmiany były wprowadzane wsadowo. Zalety:
Wady: