お知らせ
2017年5月19日
SQLServerの共通テーブル式(CTE)を使用した再帰SQLでパスなどの階層情報を組み立てる(フォルダパス、パンくず)
~~ SQLServerの再帰クエリ(CTE)を利用し、カラムデータを再帰的に取得する方法について ~~
例えば、以下の様な1カラムのデータを連結したレコードを1度のクエリ発行で取得する方法について紹介します。
アースリンク/管理部/2016年度_資料
【データ準備】
●テーブル情報
テーブルに以下のレコードを作成します。
id | parentId | name |
1 | (NULL) | アースリンク |
2 | 1 | 管理部 |
3 | 1 | BS部 |
4 | 2 | 2013年度_資料 |
5 | 2 | 2014年度_資料 |
6 | 2 | 2015年度_資料 |
7 | 2 | 2013年度_資料 |
8 | 3 | 2014年度_資料 |
9 | 3 | 2015年度_資料 |
10 | 3 | 2013年度_資料 |
11 | 3 | 2014年度_資料 |
●階層情報の「パス」を組み立てる再帰SQL
WITH cte_path(id, parentId, layerLevel, path) AS ( SELECT id, parentId, 1 AS layer_level, CAST(name AS nvarchar(MAX)) AS path FROM test_table WHERE parentId IS NULL UNION ALL SELECT test.id AS id, test.parentId AS oya_id, cte.layerLevel + 1 AS layerLevel, cte.path + N'/' + test.name AS path FROM test_table test INNER JOIN cte_path cte ON test.parentId = cte.id ) SELECT * FROM cte_path
●実行結果
id | parentId | layerLevel | path |
1 | (NULL) | 1 | アースリンク |
2 | 1 | 2 | アースリンク/管理部 |
3 | 1 | 2 | アースリンク/BS部 |
8 | 3 | 3 | アースリンク/BS部/2014年度_資料 |
9 | 3 | 3 | アースリンク/BS部/2015年度_資料 |
10 | 3 | 3 | アースリンク/BS部/2013年度_資料 |
11 | 3 | 3 | アースリンク/BS部/2014年度_資料 |
4 | 2 | 3 | アースリンク/管理部/2013年度_資料 |
5 | 2 | 3 | アースリンク/管理部/2014年度_資料 |
6 | 2 | 3 | アースリンク/管理部/2015年度_資料 |
7 | 2 | 3 | アースリンク/管理部/2013年度_資料 |
これは、共通テーブル式(CTE)を用いて再帰的にSQLを実行する事により得られる結果となります。
更に、「管理部」で絞り込みたい場合、
●以下の様にキー情報を追加します(busho_cd)
id | parentId | name | busho_cd |
1 | (NULL) | アースリンク | (NULL) |
2 | 1 | 管理部 | 001 |
3 | 1 | BS部 | 002 |
4 | 2 | 2013年度_資料 | 001 |
5 | 2 | 2014年度_資料 | 001 |
6 | 2 | 2015年度_資料 | 001 |
7 | 2 | 2013年度_資料 | 002 |
8 | 3 | 2014年度_資料 | 002 |
9 | 3 | 2015年度_資料 | 002 |
10 | 3 | 2013年度_資料 | 002 |
11 | 3 | 2014年度_資料 | 002 |
●SQLにキー情報のカラムを追加します。
busho_cdを追加
WITH cte_path(id, parentId, layerLevel, path, busho_cd) AS ( SELECT id, parentId, 1 AS layer_level, CAST(name AS nvarchar(MAX)) AS path, busho_cd FROM test_table WHERE parentId IS NULL UNION ALL SELECT test.id AS id, test.parentId AS oya_id, cte.layerLevel + 1 AS layerLevel, cte.path + N'/' + test.name AS path, test.busho_cd FROM test_table test INNER JOIN cte_path cte ON test.parentId = cte.id where test.busho_cd = '001' ) SELECT * FROM cte_path
●実行結果
id | parentId | layerLevel | path | busho_cd |
1 | (NULL) | 1 | アースリンク | (NULL) |
2 | 1 | 2 | アースリンク/管理部 | 001 |
4 | 2 | 3 | アースリンク/管理部/2013年度_資料 | 001 |
5 | 2 | 3 | アースリンク/管理部/2014年度_資料 | 001 |
6 | 2 | 3 | アースリンク/管理部/2015年度_資料 | 001 |
注意点としては、階層を結合する際に使用するキー情報(Id,parentId)で絞り込んでしまうと
上手く階層が作成されなくなってしまいます。
Java等でSQL実行結果を再帰的に処理して同様の結果を得ようとすると意外に大変かと思われます。
但し、それはパフォーマンスやレコード数が少ない場合に限られます。
レコード数が数千を超えたり、ある特殊な条件で階層の組み立てを絞り込みたい場合は、上記SQLを工夫する必要があります。