Omlouváme se za velmi pozdní odpověď, ale myslím, že jsem našel elegantní řešení, které by se mohlo stát přijatelnou odpovědí na tuto otázku.
Na základě úžasného „malého hacku“, který našel @pozs, jsem přišel s řešením, které:
- řeší situaci „nečestných listů“ s velmi malým množstvím kódu (s využitím
NOT EXISTS
predikát) - vyhýbá se výpočtům/podmínkám celé úrovně
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
Aktualizovat :
Testováno s velmi jednoduchými daty to funguje, ale jak poznamenal posz v komentáři, s jeho ukázkovými daty jsou některé nepoctivé listové uzly zapomenuty. Zjistil jsem však, že s ještě složitějšími daty nefunguje ani předchozí odpověď, protože jsou zachyceny pouze podvodné listové uzly, které mají společného předka s "maximální úrovní" listovými uzly (když tam "1.2.5.8" není, " 1.2.4" a "1.2.5" chybí, protože nemají společného předka s žádným listovým uzlem "maximální úrovně".
Takže tady je nový návrh, který mísí poszovu práci s mojí extrakcí NOT EXISTS
dílčí požadavek a udělá z něj interní UNION
, využívající UNION
schopnosti deduplikace (využití porovnávací schopnosti 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;
Testováno na http://rextester.com/SMM38494;)