回答編集履歴

5

最終型

2017/06/07 03:45

投稿

yambejp
yambejp

スコア114865

test CHANGED
@@ -108,8 +108,62 @@
108
108
 
109
109
 
110
110
 
111
+ # 入れ子集合モデルのsample(プロシージャをPHPに置き換え版)
112
+
113
+ SQLのプロシージャがどうしても作れないのであれば、以下PHPで代替できます
114
+
115
+ ```PHP
116
+
117
+ $pdo->beginTransaction();
118
+
119
+ $sql ="UPDATE tbl SET level=0,l=0,r=0;\n";
120
+
121
+ $stmt= $pdo->query($sql);
122
+
123
+ $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;";
124
+
125
+ $stmt= $pdo->query($sql);
126
+
127
+ $sql ="SELECT id FROM tbl WHERE level=0 ORDER BY parent_id ASC,id DESC;";
128
+
129
+ $stmt2 = $pdo->query($sql);
130
+
131
+ while($row=$stmt2->fetch()){
132
+
133
+ $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=?;';
134
+
135
+ $stmt = $pdo->prepare($sql);
136
+
137
+ $stmt->execute([$row["id"]]);
138
+
139
+ $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=?;';
140
+
141
+ $stmt = $pdo->prepare($sql);
142
+
143
+ $stmt->execute([$row["id"]]);
144
+
145
+ }
146
+
147
+
148
+
149
+ $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;';
150
+
151
+ $stmt = $pdo->query($sql);
152
+
153
+ $rows=$stmt->fetchAll();
154
+
155
+ $pdo->commit();// 実はロールバックしてもOK $pdo->rollback();
156
+
157
+
158
+
159
+ ```
160
+
161
+
162
+
111
163
  # 入れ子集合モデルのsample
112
164
 
165
+
166
+
113
167
  隣接モデルを入れ子集合モデルに変換するために
114
168
 
115
169
  以下のプロシージャを作っておきます。

4

ついで

2017/06/07 03:45

投稿

yambejp
yambejp

スコア114865

test CHANGED
@@ -331,3 +331,23 @@
331
331
 
332
332
 
333
333
  ```
334
+
335
+
336
+
337
+ ついでなので子要素の数を調べるにはこうです
338
+
339
+
340
+
341
+ ```SQL
342
+
343
+ 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
344
+
345
+ ```
346
+
347
+ これを上記PHPの「print $row["name"];」の下の行に以下を追記するだけです
348
+
349
+ ```PHP
350
+
351
+ if($row["child"]>0) print "(".$row["child"].")";
352
+
353
+ ```

3

修正

2017/06/06 12:52

投稿

yambejp
yambejp

スコア114865

test CHANGED
@@ -36,7 +36,7 @@
36
36
 
37
37
  l int not null,
38
38
 
39
- int not null);
39
+ r int not null); /*修正*/
40
40
 
41
41
 
42
42
 
@@ -74,6 +74,40 @@
74
74
 
75
75
 
76
76
 
77
+ id|parent_id|name|sort|level|l|r
78
+
79
+ |--:|--:|:--|--:|--:|--:|--:|
80
+
81
+ 1|NULL|0|1|0|0|0
82
+
83
+ 2|NULL|1|2|0|0|0
84
+
85
+ 3|2|1-1|3|0|0|0
86
+
87
+ 4|2|1-2|4|0|0|0
88
+
89
+ 5|2|1-3|5|0|0|0
90
+
91
+ 6|NULL|2|6|0|0|0
92
+
93
+ 7|NULL|3|7|0|0|0
94
+
95
+ 8|7|3-1|8|0|0|0
96
+
97
+ 9|7|3-2|9|0|0|0
98
+
99
+ 10|9|3-2-1|10|0|0|0
100
+
101
+ 11|9|3-2-2|11|0|0|0
102
+
103
+ 12|9|3-2-3|12|0|0|0
104
+
105
+ 13|7|3-3|13|0|0|0
106
+
107
+ 14|NULL|4|14|0|0|0
108
+
109
+
110
+
77
111
  # 入れ子集合モデルのsample
78
112
 
79
113
  隣接モデルを入れ子集合モデルに変換するために
@@ -172,24 +206,128 @@
172
206
 
173
207
  ```
174
208
 
209
+ ※プロシージャ実行後
210
+
211
+ id|parent_id|name|sort|level|l|r
212
+
213
+ |--:|--:|--:|--:|--:|--:|--:|
214
+
215
+ 1|NULL|0|1|1|1|2
216
+
217
+ 2|NULL|1|2|1|3|10
218
+
219
+ 3|2|1-1|3|2|4|5
220
+
221
+ 4|2|1-2|4|2|6|7
222
+
223
+ 5|2|1-3|5|2|8|9
224
+
225
+ 6|NULL|2|6|1|11|12
226
+
227
+ 7|NULL|3|7|1|13|26
228
+
229
+ 8|7|3-1|8|2|14|15
230
+
231
+ 9|7|3-2|9|2|16|23
232
+
233
+ 10|9|3-2-1|10|3|17|18
234
+
235
+ 11|9|3-2-2|11|3|19|20
236
+
237
+ 12|9|3-2-3|12|3|21|22
238
+
239
+ 13|7|3-3|13|2|24|25
240
+
241
+ 14|NULL|4|14|1|27|28
242
+
175
243
 
176
244
 
177
245
  nameを階層で表示し、含まれる子要素数を表示
178
246
 
179
247
  COUNT1は直接の子要素数のみ、COUNT2は孫以降の要素数も含む
180
248
 
249
+
250
+
181
251
  ```SQL
182
252
 
183
- SELECT CONCAT( REPEAT( "__", t1.level -1 ) , t1.name ) AS name
184
-
185
- ,(SELECT count(*)-1 from tbl as t3 WHERE t3.l BETWEEN t1.l AND t1.r and t3.level<=t1.level+1) AS COUNT1
186
-
187
- ,(SELECT count(*)-1 from tbl as t2 WHERE t2.l BETWEEN t1.l AND t1.r) AS COUNT1
188
-
189
- FROM tbl as t2
253
+ SELECT * FROM tbl ORDER BY l
190
-
191
- ORDER BY t1.l
254
+
192
-
193
- ```
255
+ ```
256
+
194
-
257
+ phpでデータをあつめて
258
+
195
-
259
+ $rows=$stmt->fetchAll(PDO::FETCH_ASSOC);
260
+
261
+ としたとき
262
+
263
+
264
+
265
+ ```PHP
266
+
267
+ $indent=3;
268
+
269
+ $l1=0;
270
+
271
+ foreach($rows as $key=>$row){
272
+
273
+ $l2=$row["level"];
274
+
275
+ $l3=($key<count($rows)-1)?$rows[$key+1]["level"]:0;
276
+
277
+ if($l1>=$l2){
278
+
279
+ print str_repeat(" ",($l2-1)*$indent);
280
+
281
+ }
282
+
283
+ if($l1<$l2){
284
+
285
+ print PHP_EOL;
286
+
287
+ print str_repeat(" ",($l2-1)*$indent);
288
+
289
+ print "<ul>".PHP_EOL;
290
+
291
+ print str_repeat(" ",($l2-1)*$indent);
292
+
293
+ }
294
+
295
+ print "<li>";
296
+
297
+ print $row["name"];
298
+
299
+ if($l2==$l3){
300
+
301
+ print "</li>".PHP_EOL;;
302
+
303
+ }
304
+
305
+ if($l2>$l3){
306
+
307
+ print "</li>".PHP_EOL;
308
+
309
+ for($i=$l2-$l3-1;$i>0;$i--){
310
+
311
+ print str_repeat(" ",($i)*$indent);
312
+
313
+ print "</ul>".PHP_EOL;
314
+
315
+ print str_repeat(" ",($i-1)*$indent);
316
+
317
+ print "</li>".PHP_EOL;
318
+
319
+ }
320
+
321
+ print str_repeat(" ",($l3)*$indent);
322
+
323
+ print "</ul>".PHP_EOL;
324
+
325
+ }
326
+
327
+ $l1=$l2;
328
+
329
+ }
330
+
331
+
332
+
333
+ ```

2

sample

2017/06/06 12:32

投稿

yambejp
yambejp

スコア114865

test CHANGED
@@ -11,3 +11,185 @@
11
11
  基本的にご提示されている「経路列挙モデル」より「隣接モデル」で
12
12
 
13
13
  データをもち「入れ子集合モデル」に変換して利用するほうがラクです
14
+
15
+
16
+
17
+ # 隣接モデルのsample
18
+
19
+ 隣接モデルでは、親のidだけ保存します。
20
+
21
+ 親がいない=NULLが先頭になります
22
+
23
+ (入れ子集合モデル用にlevelと、l(左)、r(右)を用意してあります)
24
+
25
+ ```SQL
26
+
27
+ CREATE TABLE tbl(id INT NOT NULL UNIQUE,
28
+
29
+ parent_id INT NULL,
30
+
31
+ name varchar(64),
32
+
33
+ sort int,
34
+
35
+ level int NOT NULL,
36
+
37
+ l int not null,
38
+
39
+ int not null);
40
+
41
+
42
+
43
+ INSERT INTO tbl(id,parent_id, name,sort) VALUES
44
+
45
+ (1,null,'0',1),
46
+
47
+ (2,null,'1',2),
48
+
49
+ (3,2,'1-1',3),
50
+
51
+ (4,2,'1-2',4),
52
+
53
+ (5,2,'1-3',5),
54
+
55
+ (6,null,'2',6),
56
+
57
+ (7,null,'3',7),
58
+
59
+ (8,7,'3-1',8),
60
+
61
+ (9,7,'3-2',9),
62
+
63
+ (10,9,'3-2-1',10),
64
+
65
+ (11,9,'3-2-2',11),
66
+
67
+ (12,9,'3-2-3',12),
68
+
69
+ (13,7,'3-3',13),
70
+
71
+ (14,null,'4',14);
72
+
73
+ ```
74
+
75
+
76
+
77
+ # 入れ子集合モデルのsample
78
+
79
+ 隣接モデルを入れ子集合モデルに変換するために
80
+
81
+ 以下のプロシージャを作っておきます。
82
+
83
+ ```SQL
84
+
85
+ DROP PROCEDURE IF EXISTS SET_LR;
86
+
87
+ DELIMITER //
88
+
89
+ CREATE PROCEDURE SET_LR()
90
+
91
+ BEGIN
92
+
93
+ DECLARE a INT DEFAULT 0;
94
+
95
+ DECLARE done INT DEFAULT 0;
96
+
97
+ DECLARE CUR CURSOR FOR
98
+
99
+ SELECT id FROM tbl WHERE level=0 ORDER BY parent_id ASC,sort DESC,id DESC;
100
+
101
+ DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
102
+
103
+ UPDATE tbl SET level=0,l=0,r=0;
104
+
105
+ /* level,l,rを初期化*/
106
+
107
+
108
+
109
+ UPDATE tbl SET level=1,l=(SELECT @a:=@a+1 FROM (SELECT @a:=0) AS sub),r=@a:=@a+1
110
+
111
+ WHERE parent_id IS NULL
112
+
113
+ ORDER BY sort ASC,id ASC;
114
+
115
+ /* 先頭データのlevelを1とし、l,rを1ずつ足していく*/
116
+
117
+
118
+
119
+ OPEN CUR;
120
+
121
+ REPEAT
122
+
123
+ FETCH CUR INTO a;
124
+
125
+ IF NOT done THEN
126
+
127
+ SET @id=a;
128
+
129
+ 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=?';
130
+
131
+ /* 子のlに親の1+1、子のrに親のl+2,子のlevelに親のlevel+1親のrを2増やす、親のlより大きいrを2増やす */
132
+
133
+
134
+
135
+ PREPARE stmt from @sql;
136
+
137
+ EXECUTE stmt USING @id;
138
+
139
+ 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=?';
140
+
141
+ /* 親の1より大きいlの内、子以外のものを2増やす */
142
+
143
+
144
+
145
+ PREPARE stmt from @sql;
146
+
147
+ EXECUTE stmt USING @id;
148
+
149
+ END IF;
150
+
151
+ UNTIL done END REPEAT;
152
+
153
+ CLOSE CUR;
154
+
155
+ END
156
+
157
+ //
158
+
159
+ DELIMITER ;
160
+
161
+
162
+
163
+ ```
164
+
165
+ プロシージャの呼び出し
166
+
167
+ 元データを追加・削除・更新する度にプロシージャを呼び出してコンバートしてください
168
+
169
+ ```SQL
170
+
171
+ CALL SET_LR;
172
+
173
+ ```
174
+
175
+
176
+
177
+ nameを階層で表示し、含まれる子要素数を表示
178
+
179
+ COUNT1は直接の子要素数のみ、COUNT2は孫以降の要素数も含む
180
+
181
+ ```SQL
182
+
183
+ SELECT CONCAT( REPEAT( "__", t1.level -1 ) , t1.name ) AS name
184
+
185
+ ,(SELECT count(*)-1 from tbl as t3 WHERE t3.l BETWEEN t1.l AND t1.r and t3.level<=t1.level+1) AS COUNT1
186
+
187
+ ,(SELECT count(*)-1 from tbl as t2 WHERE t2.l BETWEEN t1.l AND t1.r) AS COUNT1
188
+
189
+ FROM tbl as t2
190
+
191
+ ORDER BY t1.l
192
+
193
+ ```
194
+
195
+

1

追記

2017/06/05 03:13

投稿

yambejp
yambejp

スコア114865

test CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  - コンテンツ数とは何を指しますか?
6
6
 
7
+ - 13番目の要素のidが14で、14番目の要素とかぶっていますがよいのですか?
8
+
7
9
 
8
10
 
9
11
  基本的にご提示されている「経路列挙モデル」より「隣接モデル」で