LoginSignup
1
2

More than 3 years have passed since last update.

【SQLアンチパターン2章】経路列挙・入れ子集合・閉包テーブル

Last updated at Posted at 2020-08-01

目的

再帰的な関連を持つデータ(スレッドのコメント欄など)の階層構造を格納し、クエリを実行する。

スクリーンショット 2020-08-01 8.48.37.png

↓全てに共通するAccountsテーブル

create table Accounts (
account_id serial primary key, 
name varchar(10) not null
);
insert into Accounts 
values (1,'佐藤'),(2,'鈴木'),(3,'高橋'), (4,'田中'), (5,'伊藤');

スクリーンショット 2020-08-01 8.38.58.png

経路列挙(Path Enumeration)

先祖の系譜を表す文字列を各ノードの属性として格納する。

create table Comments1 (
comment_id serial primary key, 
path varchar(1000), 
author bigint unsigned not null, 
comment text not null, 
foreign key (author) references Accounts(account_id)
);
insert into Comments1 
values (1, '1/', 1, '明日暇な人いる?'),(2, '1/2/', 2, '忙しいわ'), (3, '1/2/3/', 1, 'わかった'),(4, '1/4/', 3, '暇だよー'),(5, '1/4/5/', 4, '俺も'),(6, '1/4/6/', 1, '遊びに行こう'),(7, '1/4/6/7/', 5, '私も行きたい');

スクリーンショット 2020-08-01 8.47.45.png

コメント7とその先祖を取得

select * 
from Comments1 as c 
where '1/4/6/7/' like concat(c.path, '%');

1/4/6/%1/4/%1/%のパターンとマッチする。

スクリーンショット 2020-08-01 8.57.01.png

コメント4とその子孫を取得

select * 
from Comments1 as c 
where c.path like concat('1/4/', '%');

スクリーンショット 2020-08-01 9.03.08.png

コメント4とその子孫の発言者別のコメント数、 コメント1とその子孫の発言者別のコメント数を取得

select c.author, count(*) 
from Comments1 as c 
where c.path like concat('1/4/', '%') 
group by c.author;
select c.author, count(*) 
from Comments1 as c 
where c.path like concat('1/', '%') 
group by c.author;

スクリーンショット 2020-08-01 9.07.40.png

コメント7の子に挿入

insert into Comments1 (author, comment) 
values (1, 'よし、一緒に行こう');

スクリーンショット 2020-08-01 9.14.32.png

スクリーンショット 2020-08-01 9.15.09.png

update Comments1 
set path = concat((select x.path from (select path from Comments1 where comment_id = 7) as x), last_insert_id(), '/') 
where comment_id = last_insert_id();

last_insert_id()は、現在のセッションでの行挿入によって生成された最新のIDを返す。

スクリーンショット 2020-08-01 9.23.08.png

ちなみに、以下のSQL文でpathの一部を取得している。

select x.path 
from (select path from Comments1 where comment_id = 7) as x;

スクリーンショット 2020-08-01 9.23.39.png

入れ子集合(Nested Set)

直近の親ではなく、子孫の集合に関する情報を各ノードに格納する。

スクリーンショット 2020-08-01 9.36.45.png

create table Comments2 (
comment_id serial primary key, 
nsleft integer not null, 
nsright integer not null, 
author bigint unsigned not null, 
comment text not null, 
foreign key (author) references Accounts(account_id)
);
insert into Comments2 
values (1,1,14,1, '明日暇な人いる?'),(2,2,5,2,'忙しいわ'),(3,3,4,1,'わかった'),(4,6,13,3,'暇だよー'),(5,7,8,4,'俺も'),(6,9,12,1,'遊びに行こう'),(7,10,11,5,'私も行きたい');

スクリーンショット 2020-08-01 9.35.01.png

コメント4の子孫を取得

select c2.* 
from Comments2 as c1 
inner join Comments2 as c2 
on c2.nsleft between c1.nsleft and c1.nsright 
where c1.comment_id = 4;

コメント4のノードのnsleftnsrightの間にnsleftが含まれるノードを検索する。

スクリーンショット 2020-08-01 9.52.54.png

コメント6とその先祖を取得

select c2.* 
from Comments2 as c1 
inner join Comments2 as c2 
on c1.nsleft between c2.nsleft and c2.nsright 
where c1.comment_id = 6;

コメント6のノードのnsleftを、nsleftnsrightの間に含むノードを検索する。

スクリーンショット 2020-08-01 9.59.57.png

コメント6の親を取得

select parent.* 
from Comments2 as c 
inner join Comments2 as parent 
on parent.nsleft < c.nsleft and c.nsleft < parent.nsright 
left outer join Comments2 as in_between 
on in_between.nsleft < c.nsleft and c.nsleft < in_between.nsright 
and parent.nsleft < in_between.nsleft and in_between.nsleft < parent.nsright 
where c.comment_id = 6 
and in_between.comment_id is null;

スクリーンショット 2020-08-01 10.05.18.png

閉包テーブル(Closuer Table)

直近の親子関係だけではなく、ツリー全体のパスを格納する。

スクリーンショット 2020-08-01 10.27.24.png

create table Comments3 (
comment_id serial primary key, 
author bigint unsigned not null, 
comment text not null, 
foreign key (author) references Accounts(account_id)
);
create table TreePaths (
ancestor bigint unsigned not null, 
descendant bigint unsigned not null, 
primary key (ancestor, descendant), 
foreign key (ancestor) references Comments3(comment_id), 
foreign key (descendant) references Comments3(comment_id)
);
insert into TreePaths 
values (1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),(2,2),(2,3),(3,3),(4,4),(4,5),(4,6),(4,7),(5,5),(6,6),(6,7),(7,7);

スクリーンショット 2020-08-01 10.23.26.png

スクリーンショット 2020-08-01 10.29.49.png

コメント4とその子孫を取得

select c.* 
from Comments3 as c 
inner join TreePaths as t on c.comment_id = t.descendant 
where t.ancestor = 4;

スクリーンショット 2020-08-01 10.31.25.png

コメント6とその先祖を取得

select c.* 
from Comments3 as c 
inner join TreePaths as t on comment_id = t.ancestor 
where t.descendant = 6;

スクリーンショット 2020-08-01 10.33.36.png

コメント5の子を挿入

insert into Comments3 values(8,1,いいね);
insert into TreePaths 
select t.ancestor, 8 
from TreePaths as t 
where t.descendant = 5 
union all 
select 8, 8;

スクリーンショット 2020-08-01 10.41.30.png

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2