Desculpe a resposta tardia, mas acho que encontrei uma solução elegante que pode se tornar uma resposta aceita para essa pergunta.
Com base no incrível "pequeno hack" encontrado por @pozs, criei uma solução que:
- resolve a situação de "folhas desonestas" com muito pouco código (aproveitando o
NOT EXISTS
predicado) - evita todo o cálculo/condição de nível
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
Atualizar :
Testado com dados muito simples, funciona, mas como posz apontou em um comentário, com seus dados de amostra, alguns nós de folha desonestos são esquecidos. Mas descobri que, com dados ainda mais complexos, a resposta anterior também não está funcionando, porque apenas nós de folha desonestos com um ancestral comum com nós de folha de "nível máximo" são capturados (quando "1.2.5.8" não está lá, " 1.2.4" e "1.2.5" estão ausentes porque não têm ancestral comum com nenhum nó folha de "nível máximo").
Então aqui está uma nova proposta, misturando o trabalho de posz com o meu extraindo o
NOT EXISTS
subrequest e tornando-o um UNION
interno , aproveitando UNION
habilidades de deduplicação (alavancando as habilidades de comparação 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;
Testado em http://rextester.com/SMM38494;)