トップ 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

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('%',分類名,'%')
;
+-----------------+-----------+
| 名前            | 分類名    |
+-----------------+-----------+
| 山田 太郎      | りんご    |
| 山田 太郎      | バナナ    |
| 山田 太郎      | みかん    |
| 斎藤 花子      | 苺        |
| 斎藤 花子      | 無花果    |
+-----------------+-----------+