Write element's ancestry to Postgres table from adjacency list

前端 未结 2 941
我寻月下人不归
我寻月下人不归 2021-01-21 13:34

I want to write a 1 to n-hierarchy that\'s stored as an adjacency list to a table that lists each of an element\'s ancestors. I\'m using a Postgres database (Postgres 10, but th

相关标签:
2条回答
  • 2021-01-21 14:04

    if the position of the object is not changed in time (i.e. if it was put from the beginning on level 6 it will stay on that level forever) you can introduce some sane id with 7 numbers, showing 7 levels, separated by lets say semicolon(:):

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

    and then introduce some functional indexes, like:

    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])
    

    then you can alaways make an efficient query:

    SELECT id, regexp_split_to_array(id, '\\:')[1] as level1, regexp_split_to_array(id, '\\:')[2] as level2, ...
    ORDER BY level1, level2, level3 ...
    
    0 讨论(0)
  • 2021-01-21 14:23

    SQL is a strictly typed language that does not allow the number of columns returned from a SELECT to vary depending on the data it is acting upon. See e.g. Split comma separated column data into additional columns for a discussion.

    However, PostgreSQL offers you an array type that you can use to collect values of dynamic size into a single column. The following recursive CTE collects all ancestors of every row into such an array:

    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;
    

    If there's a known limit to the levels, you can select the elements from the array per column:

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

    SQL Fiddle

    0 讨论(0)
提交回复
热议问题