Съжалявам за много късния отговор, но мисля, че намерих елегантно решение, което може да се превърне в приет отговор на този въпрос.
Въз основа на страхотния „малък хак“, открит от @pozs, измислих решение, което:
- разрешава ситуацията с "измамниците" с много малко код (използвайки
NOT EXISTS
предикат) - избягва изчисленията/условията на цялото ниво
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
Актуализиране :
Тестван с много прости данни, той работи, но както Posz посочи в коментар, с неговите примерни данни, някои измамни листни възли са забравени. Но установих, че с още по-сложни данни предишният отговор също не работи, тъй като се хващат само измамни листни възли, които имат общ предшественик с листни възли на „максимално ниво“ (когато „1.2.5.8“ не е там, " 1.2.4" и "1.2.5" отсъстват, тъй като нямат общ предшественик с нито един листен възел на "макс. ниво".
Ето едно ново предложение, смесващо работата на posz с моята чрез извличане на NOT EXISTS
подзаявка и го превръща във вътрешен UNION
, като се използва UNION
способности за премахване на дублиране (използване на способности за сравнение на 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;
Тествано на http://rextester.com/SMM38494;)