乍一看你的问题使用递归查询听起来很简单,但这并不是因为可能有同一个主管的员工列表,这需要一个聚合函数来构建相应的json数组,而是递归查询中不允许使用聚合函数...
第一步 - 我们为每个员工构建 json 对象,并在他/她是主管时构建相应的受监管员工数组:
SELECT s.id AS father
, CASE WHEN array_agg(e.id) = array[NULL :: integer] THEN NULL ELSE array_agg(e.id) END AS children_list
, jsonb_build_object
( 'id', s.id
, 'name', s.name
, 'supervises', array_agg(e.id)
) AS json_tree
FROM employee AS s
LEFT JOIN employee AS e
ON s.id = e.supervisor_id
GROUP BY s.id, s.name
第二步 - 使用递归查询,我们以自上而下的方式遍历员工树,我们为每个员工分配一个等级并构建将在下一步:
WITH RECURSIVE elt AS
(
SELECT s.id AS father
, CASE WHEN array_agg(e.id) = array[NULL :: integer] THEN NULL ELSE array_agg(e.id) END AS children_list
, jsonb_build_object
( 'id', s.id
, 'name', s.name
, 'supervises', array_agg(e.id)
) AS json_tree
FROM employee AS s
LEFT JOIN employee AS e
ON s.id = e.supervisor_id
GROUP BY s.id, s.name
), list (father, json_tree, children_list, rank, path) AS
(
SELECT c.father, c.json_tree, c.children_list, 1, '{}' :: text[]
FROM elt AS c
LEFT JOIN elt AS f
ON array[c.father] <@ f.children_list
WHERE f.father IS NULL
UNION ALL
SELECT c.father
, c.json_tree
, c.children_list
, f.rank + 1
, f.path || array['supervises',(array_position(f.children_list, c.father)-1) :: text]
FROM list AS f
INNER JOIN elt AS c
ON array[c.father] <@ f.children_list
) --, ordered_list AS (
SELECT *
FROM list
ORDER BY rank DESC
第三步 - 我们创建 jsonb_set 函数的聚合版本,以便在迭代前一个结果列表时构建最终的 jsonb 数据
CREATE OR REPLACE FUNCTION jsonb_set(x jsonb, y jsonb, p text[], e jsonb, b boolean)
RETURNS jsonb LANGUAGE sql AS $$
SELECT CASE WHEN x IS NULL THEN e ELSE jsonb_set(x, p, e, b) END ; $$ ;
CREATE OR REPLACE AGGREGATE jsonb_set_agg(x jsonb, p text[], e jsonb, b boolean)
( STYPE = jsonb, SFUNC = jsonb_set) ;
最终查询
WITH RECURSIVE elt AS
(
SELECT s.id AS father
, CASE WHEN array_agg(e.id) = array[NULL :: integer] THEN NULL ELSE array_agg(e.id) END AS children_list
, jsonb_build_object
( 'id', s.id
, 'name', s.name
, 'supervises', array_agg(e.id)
) AS json_tree
FROM employee AS s
LEFT JOIN employee AS e
ON s.id = e.supervisor_id
GROUP BY s.id, s.name
), list (father, json_tree, children_list, rank, path) AS
(
SELECT c.father, c.json_tree, c.children_list, 1, '{}' :: text[]
FROM elt AS c
LEFT JOIN elt AS f
ON array[c.father] <@ f.children_list
WHERE f.father IS NULL
UNION ALL
SELECT c.father
, c.json_tree
, c.children_list
, f.rank + 1
, f.path || array['supervises',(array_position(f.children_list, c.father)-1) :: text]
FROM list AS f
INNER JOIN elt AS c
ON array[c.father] <@ f.children_list
)
SELECT jsonb_set_agg(NULL :: jsonb, path, json_tree, true ORDER BY rank ASC)
FROM list
测试结果在dbfiddle