Scusa per la risposta molto tardiva, ma penso di aver trovato una soluzione elegante che potrebbe diventare una risposta accettata per questa domanda.
Basandomi sul fantastico "piccolo trucco" trovato da @pozs, ho trovato una soluzione che:
- risolve la situazione delle "foglie canaglia" con pochissimo codice (sfruttando il
NOT EXISTS
predicato) - evita il calcolo/condizione di livello intero
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
Aggiorna :
Testato con dati molto semplici, funziona, ma come ha sottolineato posz in un commento, con i suoi dati di esempio, alcuni nodi foglia non autorizzati vengono dimenticati. Ma ho scoperto che con dati ancora più complessi, anche la risposta precedente non funziona, perché vengono catturati solo i nodi foglia canaglia che hanno un antenato comune con nodi foglia di "livello massimo" (quando "1.2.5.8" non è presente ", 1.2.4" e "1.2.5" sono assenti perché non hanno antenati comuni con nessun nodo foglia di "livello massimo").
Quindi ecco una nuova proposta, mescolando il lavoro di posz con il mio estraendo il NOT EXISTS
sottorichiesta e trasformandola in un UNION
interno , sfruttando UNION
capacità di deduplicazione (sfruttando le capacità di confronto di 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;
Testato su http://rextester.com/SMM38494;)