MySQL 8
待ってました。mysqlで再帰SQLが使えるようになった。ORACLEではかなり前からあった。羨ましかった。早速使ってみた。これでSQLだけで部品展開ができる。
部品展開
mysql> with recursive product as ( select 親部品コード ,子部品コード ,員数 ,1 level ,員数 as 必要数 ,concat('/',親部品コード,'/',子部品コード) as path from 構成表 where 親部品コード = 'A1' union all select 構成表.親部品コード ,構成表.子部品コード ,構成表.員数 ,product.level + 1 as level ,product.必要数 * 構成表.員数 ,concat(product.path,'/',構成表.子部品コード) as path from 構成表 ,product where 構成表.親部品コード = product.子部品コード ) select * from product order by path;
+--------------------+--------------------+--------+-------+-----------+--------------------------------------------+ | 親部品コード | 子部品コード | 員数 | level | 必要数 | path | +--------------------+--------------------+--------+-------+-----------+--------------------------------------------+ | A1 | B1 | 2 | 1 | 2 | /A1/B1 | | B1 | B2 | 2 | 2 | 4 | /A1/B1/B2 | | B1 | C1 | 10 | 2 | 20 | /A1/B1/C1 | | B1 | C2 | 2 | 2 | 4 | /A1/B1/C2 | | A1 | frame_tree | 1 | 1 | 1 | /A1/frame_tree | | frame_tree | CSS | 2 | 2 | 2 | /A1/frame_tree/CSS | | frame_tree | frame.cgi | 2 | 2 | 2 | /A1/frame_tree/frame.cgi | | frame_tree | js | 2 | 2 | 2 | /A1/frame_tree/js | | frame_tree | tree02.cgi | 3 | 2 | 3 | /A1/frame_tree/tree02.cgi | | tree02.cgi | ajax.pm | 2 | 3 | 6 | /A1/frame_tree/tree02.cgi/ajax.pm | | ajax.pm | dom.js | 1 | 4 | 6 | /A1/frame_tree/tree02.cgi/ajax.pm/dom.js | | ajax.pm | screen.css | 1 | 4 | 6 | /A1/frame_tree/tree02.cgi/ajax.pm/screen.c | | ajax.pm | tree.css | 1 | 4 | 6 | /A1/frame_tree/tree02.cgi/ajax.pm/tree.css | | ajax.pm | treeview.js | 1 | 4 | 6 | /A1/frame_tree/tree02.cgi/ajax.pm/treeview | | ajax.pm | yahoo.js | 1 | 4 | 6 | /A1/frame_tree/tree02.cgi/ajax.pm/yahoo.js | | tree02.cgi | mmt.pm | 2 | 3 | 6 | /A1/frame_tree/tree02.cgi/mmt.pm | +--------------------+--------------------+--------+-------+-----------+--------------------------------------------+ 16 rows in set (0.01 sec) mysql>
mysql> show create table 構成表 \G *************************** 1. row *************************** Table: 構成表 Create Table: CREATE TABLE `構成表` ( `親部品コード` varchar(20) NOT NULL DEFAULT '', `子部品コード` varchar(20) NOT NULL DEFAULT '', `員数` decimal(10,0) NOT NULL, `乗数` decimal(10,0) NOT NULL, `UPD_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`親部品コード`,`子部品コード`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec) mysql>
エラーがでる
ERROR 1406 (22001): Data too long for column 'path' at row 2
- sql modeを変更しないとエラーがでるのでsql modeを設定後に再実行
mysql> set session sql_mode = '';
- ステートメントが切り捨てやエラーを生成しないように、この問題に対処するには、非再帰的なSELECTでCAST()を使用して、列を広くします。(https://dev.mysql.com/doc/refman/8.0/en/with.html より)
mysql> with recursive product as ( select 親部品コード ,子部品コード ,員数 ,1 level ,員数 as 必要数 ,cast(concat('/',親部品コード,'/',子部品コード) as char(100)) as path from 構成表 where 親部品コード = 'A1' union all select 構成表.親部品コード ,構成表.子部品コード ,構成表.員数 ,product.level + 1 as level ,product.必要数 * 構成表.員数 ,concat(product.path,'/',構成表.子部品コード) as path from 構成表 ,product where 構成表.親部品コード = product.子部品コード ) select * from product order by path;
+--------------------+--------------------+--------+-------+-----------+-----------------------------------------------+ | 親部品コード | 子部品コード | 員数 | level | 必要数 | path | +--------------------+--------------------+--------+-------+-----------+-----------------------------------------------+ | A1 | B1 | 1 | 1 | 1 | /A1/B1 | | B1 | B2 | 0 | 2 | 0 | /A1/B1/B2 | | B1 | C1 | 1 | 2 | 1 | /A1/B1/C1 | | B1 | C2 | 1 | 2 | 1 | /A1/B1/C2 | | A1 | frame_tree | 0 | 1 | 0 | /A1/frame_tree | | frame_tree | CSS | 0 | 2 | 0 | /A1/frame_tree/CSS | | frame_tree | frame.cgi | 0 | 2 | 0 | /A1/frame_tree/frame.cgi | | frame_tree | js | 0 | 2 | 0 | /A1/frame_tree/js | | frame_tree | tree02.cgi | 0 | 2 | 0 | /A1/frame_tree/tree02.cgi | | tree02.cgi | ajax.pm | 0 | 3 | 0 | /A1/frame_tree/tree02.cgi/ajax.pm | | ajax.pm | dom.js | 0 | 4 | 0 | /A1/frame_tree/tree02.cgi/ajax.pm/dom.js | | ajax.pm | screen.css | 0 | 4 | 0 | /A1/frame_tree/tree02.cgi/ajax.pm/screen.css | | ajax.pm | tree.css | 0 | 4 | 0 | /A1/frame_tree/tree02.cgi/ajax.pm/tree.css | | ajax.pm | treeview.js | 0 | 4 | 0 | /A1/frame_tree/tree02.cgi/ajax.pm/treeview.js | | ajax.pm | yahoo.js | 0 | 4 | 0 | /A1/frame_tree/tree02.cgi/ajax.pm/yahoo.js | | tree02.cgi | mmt.pm | 0 | 3 | 0 | /A1/frame_tree/tree02.cgi/mmt.pm | +--------------------+--------------------+--------+-------+-----------+-----------------------------------------------+ 16 rows in set (0.00 sec)
カンマ区切りの文字列を行に変換
再帰SQL
1つのカラムにカンマ区切りで入っている複数の単語を分割し行に(縦展開)展開する。(※設計時に正規化しとけって話ですが・・)
select 名前,好きなもの from 好き嫌い;
+-----------------+-----------------------------------------+ | 名前 | 好きなもの | +-----------------+-----------------------------------------+ | 山田 太郎 | りんご,バナナ,みかん,いちご | | 斎藤 花子 | 苺,無花果 | +-----------------+-----------------------------------------+
再帰共通テーブル式を使いカンマ区切りの文字列を分解
- 再帰共通テーブル式はwith recursiveから始まる。
- SUBSTRINGでカンマの前と後で最初の単語と残りの文字列に分解する。(カンマが無いと分解できないので文字列の最後にカンマを追加)
- UNION ALLの次のSELECT文で残りの文字列に文字がある間共通テーブルを参照し残りの文字列をカンマの前と後ろで分解する。
with recursive work as ( select 名前 ,SUBSTRING(concat(好きなもの,","),1,LOCATE(',',concat(好きなもの,","))-1) AS 最初の単語 ,SUBSTRING(concat(好きなもの,","),LOCATE(',',concat(好きなもの,","))+1,LENGTH(concat(好きなもの,","))) AS 残りの文字列 from 好き嫌い union all select 名前 ,SUBSTRING(残りの文字列,1,LOCATE(',',残りの文字列)-1) AS 最初の単語 ,SUBSTRING(残りの文字列,LOCATE(',',残りの文字列)+1,LENGTH(残りの文字列)) AS 残りの文字列 from work where 残りの文字列 <> '' ) select 名前,最初の単語 as 好物 from work ;
+-----------------+-----------+ | 名前 | 好物 | +-----------------+-----------+ | 山田 太郎 | りんご | | 斎藤 花子 | 苺 | | 山田 太郎 | バナナ | | 斎藤 花子 | 無花果 | | 山田 太郎 | みかん | | 山田 太郎 | いちご | +-----------------+-----------+
テーブル参照
カンマで区切られた単語でテーブルを参照できる場合はテーブルを参照し縦展開する。
select 名前,分類名 from 好き嫌い left join 分類名称 on 好きなもの like concat('%',分類名,'%') ;
+-----------------+-----------+ | 名前 | 分類名 | +-----------------+-----------+ | 山田 太郎 | りんご | | 山田 太郎 | バナナ | | 山田 太郎 | みかん | | 斎藤 花子 | 苺 | | 斎藤 花子 | 無花果 | +-----------------+-----------+
- 無精・短気・傲慢