碰到乙個樹形資料需要儲存再資料控制,碰到以下兩個問題:
為了更加簡單一些,我們將使用一下資料
當設計自引用表(有時候自己join自己)。最簡單明瞭的就是有乙個section a
|--- section a.1
section b
|--- section b.1
|--- section b.1
|--- section b.1.1
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
標識頂節點。
ok,一切搞定,我們可以用ltree操作符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');
@>
和<@
來查詢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...