teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

5

最終型

2017/06/07 03:45

投稿

yambejp
yambejp

スコア117906

answer CHANGED
@@ -53,7 +53,34 @@
53
53
  13|7|3-3|13|0|0|0
54
54
  14|NULL|4|14|0|0|0
55
55
 
56
+ # 入れ子集合モデルのsample(プロシージャをPHPに置き換え版)
57
+ SQLのプロシージャがどうしても作れないのであれば、以下PHPで代替できます
58
+ ```PHP
59
+ $pdo->beginTransaction();
60
+ $sql ="UPDATE tbl SET level=0,l=0,r=0;\n";
61
+ $stmt= $pdo->query($sql);
62
+ $sql ="UPDATE tbl SET level=1,l=(SELECT @a:=@a+1 FROM (SELECT @a:=0) AS sub),r=@a:=@a+1 WHERE parent_id IS NULL ORDER BY id;";
63
+ $stmt= $pdo->query($sql);
64
+ $sql ="SELECT id FROM tbl WHERE level=0 ORDER BY parent_id ASC,id DESC;";
65
+ $stmt2 = $pdo->query($sql);
66
+ while($row=$stmt2->fetch()){
67
+ $sql ='UPDATE tbl as a1,tbl as a2,tbl as a3 SET a1.l=a2.l+1,a1.r=a2.l+2,a1.level=a2.level+1,a2.r=a2.r+2,a3.r=a3.r+2 WHERE a1.parent_id=a2.id AND a2.l<a3.r AND a1.id=?;';
68
+ $stmt = $pdo->prepare($sql);
69
+ $stmt->execute([$row["id"]]);
70
+ $sql ='UPDATE tbl as a1,tbl as a2,tbl as a3 SET a3.l=a3.l+2 WHERE a1.parent_id=a2.id AND a2.l<a3.l and a3.id!=a1.id AND a1.id=?;';
71
+ $stmt = $pdo->prepare($sql);
72
+ $stmt->execute([$row["id"]]);
73
+ }
74
+
75
+ $sql ='SELECT name,level,(SELECT COUNT(*)-1 FROM tbl AS t2 WHERE t2.l BETWEEN t1.l AND t1.r) AS child FROM tbl AS t1 ORDER BY l;';
76
+ $stmt = $pdo->query($sql);
77
+ $rows=$stmt->fetchAll();
78
+ $pdo->commit();// 実はロールバックしてもOK $pdo->rollback();
79
+
80
+ ```
81
+
56
82
  # 入れ子集合モデルのsample
83
+
57
84
  隣接モデルを入れ子集合モデルに変換するために
58
85
  以下のプロシージャを作っておきます。
59
86
  ```SQL

4

ついで

2017/06/07 03:45

投稿

yambejp
yambejp

スコア117906

answer CHANGED
@@ -164,4 +164,14 @@
164
164
  $l1=$l2;
165
165
  }
166
166
 
167
+ ```
168
+
169
+ ついでなので子要素の数を調べるにはこうです
170
+
171
+ ```SQL
172
+ SELECT name,level,(SELECT COUNT(*)-1 FROM tbl AS t2 WHERE t2.l BETWEEN t1.l AND t1.r) AS child FROM tbl AS t1 ORDER BY l
173
+ ```
174
+ これを上記PHPの「print $row["name"];」の下の行に以下を追記するだけです
175
+ ```PHP
176
+ if($row["child"]>0) print "(".$row["child"].")";
167
177
  ```

3

修正

2017/06/06 12:52

投稿

yambejp
yambejp

スコア117906

answer CHANGED
@@ -17,7 +17,7 @@
17
17
  sort int,
18
18
  level int NOT NULL,
19
19
  l int not null,
20
- int not null);
20
+ r int not null); /*修正*/
21
21
 
22
22
  INSERT INTO tbl(id,parent_id, name,sort) VALUES
23
23
  (1,null,'0',1),
@@ -36,6 +36,23 @@
36
36
  (14,null,'4',14);
37
37
  ```
38
38
 
39
+ id|parent_id|name|sort|level|l|r
40
+ |--:|--:|:--|--:|--:|--:|--:|
41
+ 1|NULL|0|1|0|0|0
42
+ 2|NULL|1|2|0|0|0
43
+ 3|2|1-1|3|0|0|0
44
+ 4|2|1-2|4|0|0|0
45
+ 5|2|1-3|5|0|0|0
46
+ 6|NULL|2|6|0|0|0
47
+ 7|NULL|3|7|0|0|0
48
+ 8|7|3-1|8|0|0|0
49
+ 9|7|3-2|9|0|0|0
50
+ 10|9|3-2-1|10|0|0|0
51
+ 11|9|3-2-2|11|0|0|0
52
+ 12|9|3-2-3|12|0|0|0
53
+ 13|7|3-3|13|0|0|0
54
+ 14|NULL|4|14|0|0|0
55
+
39
56
  # 入れ子集合モデルのsample
40
57
  隣接モデルを入れ子集合モデルに変換するために
41
58
  以下のプロシージャを作っておきます。
@@ -85,13 +102,66 @@
85
102
  ```SQL
86
103
  CALL SET_LR;
87
104
  ```
105
+ ※プロシージャ実行後
106
+ id|parent_id|name|sort|level|l|r
107
+ |--:|--:|--:|--:|--:|--:|--:|
108
+ 1|NULL|0|1|1|1|2
109
+ 2|NULL|1|2|1|3|10
110
+ 3|2|1-1|3|2|4|5
111
+ 4|2|1-2|4|2|6|7
112
+ 5|2|1-3|5|2|8|9
113
+ 6|NULL|2|6|1|11|12
114
+ 7|NULL|3|7|1|13|26
115
+ 8|7|3-1|8|2|14|15
116
+ 9|7|3-2|9|2|16|23
117
+ 10|9|3-2-1|10|3|17|18
118
+ 11|9|3-2-2|11|3|19|20
119
+ 12|9|3-2-3|12|3|21|22
120
+ 13|7|3-3|13|2|24|25
121
+ 14|NULL|4|14|1|27|28
88
122
 
89
123
  nameを階層で表示し、含まれる子要素数を表示
90
124
  COUNT1は直接の子要素数のみ、COUNT2は孫以降の要素数も含む
125
+
91
126
  ```SQL
92
- SELECT CONCAT( REPEAT( "__", t1.level -1 ) , t1.name ) AS name
93
- ,(SELECT count(*)-1 from tbl as t3 WHERE t3.l BETWEEN t1.l AND t1.r and t3.level<=t1.level+1) AS COUNT1
94
- ,(SELECT count(*)-1 from tbl as t2 WHERE t2.l BETWEEN t1.l AND t1.r) AS COUNT1
95
- FROM tbl as t2
127
+ SELECT * FROM tbl ORDER BY l
96
- ORDER BY t1.l
97
128
  ```
129
+ phpでデータをあつめて
130
+ $rows=$stmt->fetchAll(PDO::FETCH_ASSOC);
131
+ としたとき
132
+
133
+ ```PHP
134
+ $indent=3;
135
+ $l1=0;
136
+ foreach($rows as $key=>$row){
137
+ $l2=$row["level"];
138
+ $l3=($key<count($rows)-1)?$rows[$key+1]["level"]:0;
139
+ if($l1>=$l2){
140
+ print str_repeat(" ",($l2-1)*$indent);
141
+ }
142
+ if($l1<$l2){
143
+ print PHP_EOL;
144
+ print str_repeat(" ",($l2-1)*$indent);
145
+ print "<ul>".PHP_EOL;
146
+ print str_repeat(" ",($l2-1)*$indent);
147
+ }
148
+ print "<li>";
149
+ print $row["name"];
150
+ if($l2==$l3){
151
+ print "</li>".PHP_EOL;;
152
+ }
153
+ if($l2>$l3){
154
+ print "</li>".PHP_EOL;
155
+ for($i=$l2-$l3-1;$i>0;$i--){
156
+ print str_repeat(" ",($i)*$indent);
157
+ print "</ul>".PHP_EOL;
158
+ print str_repeat(" ",($i-1)*$indent);
159
+ print "</li>".PHP_EOL;
160
+ }
161
+ print str_repeat(" ",($l3)*$indent);
162
+ print "</ul>".PHP_EOL;
163
+ }
164
+ $l1=$l2;
165
+ }
166
+
167
+ ```

2

sample

2017/06/06 12:32

投稿

yambejp
yambejp

スコア117906

answer CHANGED
@@ -4,4 +4,94 @@
4
4
  - 13番目の要素のidが14で、14番目の要素とかぶっていますがよいのですか?
5
5
 
6
6
  基本的にご提示されている「経路列挙モデル」より「隣接モデル」で
7
- データをもち「入れ子集合モデル」に変換して利用するほうがラクです
7
+ データをもち「入れ子集合モデル」に変換して利用するほうがラクです
8
+
9
+ # 隣接モデルのsample
10
+ 隣接モデルでは、親のidだけ保存します。
11
+ 親がいない=NULLが先頭になります
12
+ (入れ子集合モデル用にlevelと、l(左)、r(右)を用意してあります)
13
+ ```SQL
14
+ CREATE TABLE tbl(id INT NOT NULL UNIQUE,
15
+ parent_id INT NULL,
16
+ name varchar(64),
17
+ sort int,
18
+ level int NOT NULL,
19
+ l int not null,
20
+ int not null);
21
+
22
+ INSERT INTO tbl(id,parent_id, name,sort) VALUES
23
+ (1,null,'0',1),
24
+ (2,null,'1',2),
25
+ (3,2,'1-1',3),
26
+ (4,2,'1-2',4),
27
+ (5,2,'1-3',5),
28
+ (6,null,'2',6),
29
+ (7,null,'3',7),
30
+ (8,7,'3-1',8),
31
+ (9,7,'3-2',9),
32
+ (10,9,'3-2-1',10),
33
+ (11,9,'3-2-2',11),
34
+ (12,9,'3-2-3',12),
35
+ (13,7,'3-3',13),
36
+ (14,null,'4',14);
37
+ ```
38
+
39
+ # 入れ子集合モデルのsample
40
+ 隣接モデルを入れ子集合モデルに変換するために
41
+ 以下のプロシージャを作っておきます。
42
+ ```SQL
43
+ DROP PROCEDURE IF EXISTS SET_LR;
44
+ DELIMITER //
45
+ CREATE PROCEDURE SET_LR()
46
+ BEGIN
47
+ DECLARE a INT DEFAULT 0;
48
+ DECLARE done INT DEFAULT 0;
49
+ DECLARE CUR CURSOR FOR
50
+ SELECT id FROM tbl WHERE level=0 ORDER BY parent_id ASC,sort DESC,id DESC;
51
+ DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
52
+ UPDATE tbl SET level=0,l=0,r=0;
53
+ /* level,l,rを初期化*/
54
+
55
+ UPDATE tbl SET level=1,l=(SELECT @a:=@a+1 FROM (SELECT @a:=0) AS sub),r=@a:=@a+1
56
+ WHERE parent_id IS NULL
57
+ ORDER BY sort ASC,id ASC;
58
+ /* 先頭データのlevelを1とし、l,rを1ずつ足していく*/
59
+
60
+ OPEN CUR;
61
+ REPEAT
62
+ FETCH CUR INTO a;
63
+ IF NOT done THEN
64
+ SET @id=a;
65
+ SET @sql='UPDATE tbl as a1,tbl as a2,tbl as a3 SET a1.l=a2.l+1,a1.r=a2.l+2,a1.level=a2.level+1,a2.r=a2.r+2,a3.r=a3.r+2 WHERE a1.parent_id=a2.id AND a2.l<a3.r AND a1.id=?';
66
+ /* 子のlに親の1+1、子のrに親のl+2,子のlevelに親のlevel+1親のrを2増やす、親のlより大きいrを2増やす */
67
+
68
+ PREPARE stmt from @sql;
69
+ EXECUTE stmt USING @id;
70
+ SET @sql='UPDATE tbl as a1,tbl as a2,tbl as a3 SET a3.l=a3.l+2 WHERE a1.parent_id=a2.id AND a2.l<a3.l and a3.id!=a1.id AND a1.id=?';
71
+ /* 親の1より大きいlの内、子以外のものを2増やす */
72
+
73
+ PREPARE stmt from @sql;
74
+ EXECUTE stmt USING @id;
75
+ END IF;
76
+ UNTIL done END REPEAT;
77
+ CLOSE CUR;
78
+ END
79
+ //
80
+ DELIMITER ;
81
+
82
+ ```
83
+ プロシージャの呼び出し
84
+ 元データを追加・削除・更新する度にプロシージャを呼び出してコンバートしてください
85
+ ```SQL
86
+ CALL SET_LR;
87
+ ```
88
+
89
+ nameを階層で表示し、含まれる子要素数を表示
90
+ COUNT1は直接の子要素数のみ、COUNT2は孫以降の要素数も含む
91
+ ```SQL
92
+ SELECT CONCAT( REPEAT( "__", t1.level -1 ) , t1.name ) AS name
93
+ ,(SELECT count(*)-1 from tbl as t3 WHERE t3.l BETWEEN t1.l AND t1.r and t3.level<=t1.level+1) AS COUNT1
94
+ ,(SELECT count(*)-1 from tbl as t2 WHERE t2.l BETWEEN t1.l AND t1.r) AS COUNT1
95
+ FROM tbl as t2
96
+ ORDER BY t1.l
97
+ ```

1

追記

2017/06/05 03:13

投稿

yambejp
yambejp

スコア117906

answer CHANGED
@@ -1,6 +1,7 @@
1
1
  - なぜ最初の階層はul-liなのに、2段目はdl-dt-ddとなり
2
2
  3段目でul-liにもどっているのでしょうか?
3
3
  - コンテンツ数とは何を指しますか?
4
+ - 13番目の要素のidが14で、14番目の要素とかぶっていますがよいのですか?
4
5
 
5
6
  基本的にご提示されている「経路列挙モデル」より「隣接モデル」で
6
7
  データをもち「入れ子集合モデル」に変換して利用するほうがラクです