从邻接列表

时间:2018-02-07 11:43:56

标签: sql postgresql hierarchical-data recursive-query

我想写一个1到n层次结构,它作为邻接列表存储到列出每个元素祖先的表中。我使用Postgres数据库(Postgres 10,但部署代码的机器运行Postgres 9.x)。

示例输入表(邻接列表):

INSERT INTO public.test (id, name, parent_id)
VALUES (1, 't', 1),
   (11, 't1', 1),
   (12, 't2', 1),
   (13, 't3', 1),
   (111, 't11', 11),
   (112, 't12', 11),
   (121, 't21', 12),
   (14, 't4', 1),
   (141, 't41', 14),
   (142, 't42', 14)

因此,我想要一个看起来像这样的表(只显示几行;此外,我试图解决的现实生活中的问题有七个层级而不是两个):

+-----+-------+--------+--------+
| id  | level | level0 | level1 |
+-----+-------+--------+--------+
|   1 |     0 | NULL   | NULL   |
|  11 |     1 | 1      | NULL   |
|  12 |     1 | 1      | NULL   |
| 111 |     2 | 1      | 11     |
+-----+-------+--------+--------+

id是元素的id,level是此元素在层次结构中的级别(0是根级别),level0/1是元素& #39;各自级别的祖先。

我是SQL的新手,所以我没有得到任何可以告诉你的代码。谷歌搜索告诉我,我可能需要使用递归CTE来获得所需的结果并执行自我加入,但我还没有能够弄清楚如何去做。谢谢你的帮助。

修改

这是我到目前为止所尝试的:

WITH RECURSIVE cte AS
(
SELECT m.id AS id,
    0 AS level,
    m.parent_id AS level0,
    m.parent_id AS level1,
    m.parent_id AS parent
    FROM public.test AS m
    WHERE m.parent_id IS NULL

UNION ALL

SELECT 
    m.id,
    cte.level + 1,
    cte.parent AS level0,
    cte.parent AS level1,
    m.parent_id AS parent
    FROM public.test AS m 
        INNER JOIN cte
            ON m.parent_id = cte.id 
)
SELECT *
FROM cte;

当然,将level0level1设置为元素的父级并不会产生所需的结果,但我不得不将其设置为某个东西并避开比这更进一步。

2 个答案:

答案 0 :(得分:1)

如果对象的位置没有及时改变(例如,如果从第6级开始放置它将永远保持在那个级别)你可以引入一些7个数字的健全id,显示7个级别,由让我们说分号(:):

' 1:1:1:1:1:1:1'

然后介绍一些功能索引,如:

CREATE INDEX level1_idx ON main_table USING (regexp_split_to_array(id, '\\:')[1])
CREATE INDEX level2_idx ON main_table USING (regexp_split_to_array(id, '\\:')[2])
CREATE INDEX level3_idx ON main_table USING (regexp_split_to_array(id, '\\:')[3])

然后你可以进行有效的查询:

SELECT id, regexp_split_to_array(id, '\\:')[1] as level1, regexp_split_to_array(id, '\\:')[2] as level2, ...
ORDER BY level1, level2, level3 ...

答案 1 :(得分:1)

SQL是一种严格类型的语言,不允许SELECT返回的列数根据其所处理的数据而变化。参见例如Split comma separated column data into additional columns进行讨论。

但是,PostgreSQL为您提供array type,您可以使用它来将动态大小的值收集到一个列中。以下递归CTE将每行的所有祖先收集到这样的数组中:

with recursive rec(id, level, parent_id, ancestors) as (
  select id, 0, parent_id, array[] :: int[]
  from test
  where parent_id = id
  union all
  select t.id, rec.level + 1, t.parent_id, rec.ancestors || array[t.parent_id]
  from test t
  join rec on t.parent_id = rec.id
  where t.parent_id <> t.id
)
select 
  rec.id,
  rec.level,
  rec.ancestors
from rec;

如果已知级别的限制,您可以从每列的数组中选择元素:

select
  rec.id,
  rec.level,
  rec.ancestors[1] level1,
  rec.ancestors[2] level2,
  ...

SQL Fiddle