Perdón por la respuesta tan tardía, pero creo que encontré una solución elegante que podría convertirse en una respuesta aceptada para esta pregunta.
Basado en el increíble "pequeño truco" encontrado por @pozs, se me ocurrió una solución que:
- resuelve la situación de "hojas rebeldes" con muy poco código (aprovechando el
NOT EXISTS
predicado) - evita todo el cálculo de nivel/condiciones
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
Actualizar :
Probado con datos muy simples, funciona, pero como posz señaló en un comentario, con sus datos de muestra, se olvidan algunos nodos hoja maliciosos. Pero descubrí que con datos aún más complejos, la respuesta anterior tampoco funciona, porque solo se capturan los nodos de hoja no autorizados que tienen un ancestro común con nodos de hoja de "nivel máximo" (cuando "1.2.5.8" no está allí, " 1.2.4" y "1.2.5" están ausentes porque no tienen un ancestro común con ningún nodo hoja de "nivel máximo".
Así que aquí hay una nueva propuesta, mezclando el trabajo de posz con el mío extrayendo el NOT EXISTS
subsolicitud y convirtiéndola en una UNION
interna , aprovechando UNION
habilidades de deduplicación (aprovechando las habilidades de comparación de 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;
Probado en http://rextester.com/SMM38494;)