Postgres儲存樹形資料

2021-09-02 15:36:48 字數 3946 閱讀 6289

碰到乙個樹形資料需要儲存再資料控制,碰到以下兩個問題:

為了更加簡單一些,我們將使用一下資料

section a

|--- section a.1

section b

|--- section b.1

|--- section b.1

|--- section b.1.1

當設計自引用表(有時候自己join自己)。最簡單明瞭的就是有乙個parent_id字段。

create table section (

id integer primary key,

name text,

parent_id integer references section,

);alter table page add column parent_id integer references page;

create index section_parent_id_idx on section (parent_id);

然後插入一些樣例資料,用parent_id來關聯其他節點

insert into section (id, name, parent_id) values (1, 'section a', null);

insert into section (id, name, parent_id) values (2, 'section a.1', 1);

insert into section (id, name, parent_id) values (3, 'section b', null);

insert into section (id, name, parent_id) values (4, 'section b.1', 3);

insert into section (id, name, parent_id) values (5, 'section b.2', 3);

insert into section (id, name, parent_id) values (6, 'section b.2.1', 5);

再進行一些簡單的查詢時,這個方法非常好使。比如我們要查詢section b的所有一級子節點

select * from section where parent = 3
但是如果要做複雜一些的查詢時,就很蛋疼了,查詢中會有許多複雜和遞迴的問題。比如我們要查詢section b的所有子節點

with recursive nodes(id,name,parent_id) as (

select s1.id, s1.name, s1.parent_id

from section s1 where parent_id = 3

union

select s2.id, s2.name, s2.parent_id

from section s2, nodes s1 where s2.parent_id = s1.id

)select * from nodes;

這種方案解決了第乙個問題,但是沒有解決第二個問題(高效的找到子樹)

ltree extension來查詢樹形資料是個不錯的選擇,在自引用的關係表中表現的更加優秀。用ltree重新建乙個表。我將用每乙個section的主鍵作為ltree路徑中的標識。用root標識頂節點。

create extension ltree;

create table section (

id integer primary key,

name text,

parent_path ltree

);create index section_parent_path_idx on section using gist (parent_path);

insert into section (id, name, parent_path) values (1, 'section 1', 'root');

insert into section (id, name, parent_path) values (2, 'section 1.1', 'root.1');

insert into section (id, name, parent_path) values (3, 'section 2', 'root');

insert into section (id, name, parent_path) values (4, 'section 2.1', 'root.3');

insert into section (id, name, parent_path) values (4, 'section 2.2', 'root.3');

insert into section (id, name, parent_path) values (5, 'section 2.2.1', 'root.3.4');

ok,一切搞定,我們可以用ltree操作符@><@來查詢section b的所有子節點

select * from section where parent_path <@ 'root.3';
但是還是有一些小問題:

為了解決上章的兩個小問題,我們需要一種混搭(有parent_id還要高效易於維護)。為了達到這個目標,我們設計乙個trigger來封裝樹操作的過程,更新樹僅僅靠更新parent_id。

create extension ltree;

create table section (

id integer primary key,

name text,

parent_id integer references section,

parent_path ltree

);create index section_parent_path_idx on section using gist (parent_path);

create index section_parent_id_idx on section (parent_id);

create or replace function update_section_parent_path() returns trigger as $$

declare

path ltree;

begin

if new.parent_id is null then

new.parent_path = 'root'::ltree;

elseif tg_op = 'insert' or old.parent_id is null or old.parent_id != new.parent_id then

select parent_path || id::text from section where id = new.parent_id into path;

if path is null then

raise exception 'invalid parent_id %', new.parent_id;

end if;

new.parent_path = path;

end if;

return new;

end;

$$ language plpgsql;

create trigger parent_path_tgr

before insert or update on section

for each row execute procedure update_section_parent_path();

這樣就爽多了.^_^.

樹形資料轉換

測試資料 create table project id int,name nvarchar 20 parent id int insert project select 1,所有專案 null union all select 2,專案1 1 union all select 3,專案2 1 cr...

樹形資料轉換

測試資料 create table project id int,name nvarchar 20 parent id int insert project select 1,所有專案 null union all select 2,專案1 1 union all select 3,專案2 1 cr...

樹形資料轉換

測試資料 create table project id int,name nvarchar 20 parent id int insert project select 1,所有專案 null union all select 2,專案1 1 union all select 3,專案2 1 cr...