Przepraszam za bardzo późną odpowiedź, ale myślę, że znalazłem eleganckie rozwiązanie, które może stać się akceptowaną odpowiedzią na to pytanie.
W oparciu o niesamowity „mały hack” znaleziony przez @pozs, wymyśliłem rozwiązanie, które:
- rozwiązuje sytuację „nieuczciwych liści” za pomocą bardzo małej ilości kodu (wykorzystując
NOT EXISTS
orzeczenie) - unika wszystkich obliczeń poziomu/warunków
WITH RECURSIVE customer_area_tree("id", "customer_id", "parent_id", "name", "description", "children") AS (
-- tree leaves (no matching children)
SELECT c.*, json '[]'
FROM customer_area_node c
WHERE NOT EXISTS(SELECT * FROM customer_area_node AS hypothetic_child WHERE hypothetic_child.parent_id = c.id)
UNION ALL
-- pozs's awesome "little hack"
SELECT (parent).*, json_agg(child) AS "children"
FROM (
SELECT parent, child
FROM customer_area_tree AS child
JOIN customer_area_node parent ON parent.id = child.parent_id
) branch
GROUP BY branch.parent
)
SELECT json_agg(t)
FROM customer_area_tree t
LEFT JOIN customer_area_node AS hypothetic_parent ON(hypothetic_parent.id = t.parent_id)
WHERE hypothetic_parent.id IS NULL
Aktualizacja :
Testowany z bardzo prostymi danymi, działa, ale jak zauważył posz w komentarzu, z jego przykładowymi danymi zapomniano o niektórych nieuczciwych węzłach liści. Ale dowiedziałem się, że przy jeszcze bardziej złożonych danych poprzednia odpowiedź też nie działa, ponieważ wyłapywane są tylko nieuczciwe węzły liści mające wspólnego przodka z węzłami liści „maksymalny poziom” (gdy nie ma „1.2.5.8”, „ 1.2.4” i „1.2.5” są nieobecne, ponieważ nie mają wspólnego przodka z żadnym węzłem liścia „maksymalnego poziomu”).
Oto nowa propozycja, mieszanie pracy posz z moim przez wyodrębnienie NOT EXISTS
podżądanie i uczynienie go wewnętrzną UNION
, wykorzystując UNION
umiejętności deduplikacji (wykorzystywanie umiejętności porównywania jsonb):
<!-- language: sql -->
WITH RECURSIVE
c_with_level AS (
SELECT *, 0 as lvl
FROM customer_area_node
WHERE parent_id IS NULL
UNION ALL
SELECT child.*, parent.lvl + 1
FROM customer_area_node child
JOIN c_with_level parent ON parent.id = child.parent_id
),
maxlvl AS (
SELECT max(lvl) maxlvl FROM c_with_level
),
c_tree AS (
SELECT c_with_level.*, jsonb '[]' children
FROM c_with_level, maxlvl
WHERE lvl = maxlvl
UNION
(
SELECT (branch_parent).*, jsonb_agg(branch_child)
FROM (
SELECT branch_parent, branch_child
FROM c_with_level branch_parent
JOIN c_tree branch_child ON branch_child.parent_id = branch_parent.id
) branch
GROUP BY branch.branch_parent
UNION
SELECT c.*, jsonb '[]' children
FROM c_with_level c
WHERE NOT EXISTS (SELECT 1 FROM c_with_level hypothetical_child WHERE hypothetical_child.parent_id = c.id)
)
)
SELECT jsonb_pretty(row_to_json(c_tree)::jsonb)
FROM c_tree
WHERE lvl = 0;
Testowane na http://rextester.com/SMM38494;)