documents:database:sql-0007
差分
このページの2つのバージョン間の差分を表示します。
| 両方とも前のリビジョン前のリビジョン | |||
| documents:database:sql-0007 [2026/05/14 12:36] – [ライブツアーのセットリストを公演毎に横に並べる SQL動的生成&UNION利用編] k896951 | documents:database:sql-0007 [2026/05/17 15:31] (現在) – k896951 | ||
|---|---|---|---|
| 行 1: | 行 1: | ||
| ====== 901.ライブツアーのセットリストを公演毎に横に並べる SQL動的生成&UNION利用編 ====== | ====== 901.ライブツアーのセットリストを公演毎に横に並べる SQL動的生成&UNION利用編 ====== | ||
| - | |||
| - | |||
| - | 2015/ | ||
| - | 某『UNION使う方法もどうせなんだし載せちゃおうゼ!』\\ | ||
| - | 私『…はい』 | ||
| - | |||
| - | どうしても横方向が可変の出力に対応したい場合は、動的なSQL組み立てを行って対応します。\\ | ||
| - | 昔いた職場のメインフレーム上のRDBMSへSQLを発行する時によく使ってた手です。 | ||
| - | |||
| - | [[documents: | ||
| - | |||
| - | ===== 基本的な考えかた ===== | ||
| - | |||
| - | 固定なら難しくないのですが、可変の場合、SQLの世界だけでやるのは困難かと思います。\\ | ||
| - | いまなら、PivotやCrosstabの様な構文があるでしょうけど、それでも面倒で困難な事には変わらないかなと思われます。 | ||
| - | |||
| - | そんなめんどうな事をするぐらいなら、自分に都合のよいSQLを動的生成し、それを発行してしまえばよいのです。\\ | ||
| - | 何項目を並べるかわからない? 何レコードあるか不明? HAHAHA!、SELECT COUNT(*)でカウントしちゃえばいいじゃないですか。目的のSQL実行前に事前確認の為のSQL実行しちゃいけないなんて決まりはないはずです。 | ||
| - | |||
| - | 簡単なSQLを発行すれば、項目をいくつ横に繋ぐかが判ります。データを取得する本命のSQLを生成する時に、取得個数に合わせて項目を増やせばいいんです。 | ||
| - | |||
| - | プログラムを作る業界においては、困難より容易な方で対処するのが美徳でございます。ですので「SQLのみ」で解決するのではなく、対応が容易な「SQLを発行する側」で対処するのです。\\ | ||
| - | ※「容易だけど無駄にコスト(時間)のかかる」方法、の意ではないので間違えないでください。 | ||
| - | |||
| - | ===== 実行結果とSQL生成ロジック ===== | ||
| - | |||
| - | tour_id=227が指定されたものとして、SQLの生成ロジックと実行結果を示します。このidは[[documents: | ||
| - | |||
| - | ==== 実行結果 ==== | ||
| - | |||
| - | 生成されたSQLの実行結果はこうなります。 | ||
| - | <code text ans.txt> | ||
| - | | ||
| - | ----------------------------------+------------------------------+------------------------------+------------------------------+------------------------------+--------------------------------+------------------------------+------------------------------+------------------------------+------------------------------ | ||
| - | | ||
| - | | ||
| - | Zepp Sendai | ||
| - | | ||
| - | | ||
| - | | ||
| - | | ||
| - | | ||
| - | HONEY FLOWER | ||
| - | | ||
| - | | ||
| - | | ||
| - | POWER GATE | POWER GATE | POWER GATE | POWER GATE | POWER GATE | POWER GATE | POWER GATE | POWER GATE | POWER GATE | POWER GATE | ||
| - | What cheer? | ||
| - | | ||
| - | | ||
| - | | ||
| - | | ||
| - | still in the groove | ||
| - | | ||
| - | | ||
| - | New Sensation | ||
| - | | ||
| - | | ||
| - | The place of happiness | ||
| - | | ||
| - | | ||
| - | | New Sensation | ||
| - | (28 行) | ||
| - | |||
| - | nanamizuki=> | ||
| - | </ | ||
| - | |||
| - | ==== 生成されたSQL ==== | ||
| - | |||
| - | 生成されたSQLは以下になります。 | ||
| - | |||
| - | <code sql> | ||
| - | $ perl gensql.pl | ||
| - | -- 公演日の出力 | ||
| - | SELECT cast(max(case stage when 1 then event_date else null end) as varchar) as " | ||
| - | , | ||
| - | , | ||
| - | , | ||
| - | , | ||
| - | , | ||
| - | , | ||
| - | , | ||
| - | , | ||
| - | , | ||
| - | FROM events WHERE tour_id=227 | ||
| - | -- 開催場所の出力 | ||
| - | UNION ALL | ||
| - | | ||
| - | ,max(case t1.stage when 2 then t2.place else null end) | ||
| - | ,max(case t1.stage when 3 then t2.place else null end) | ||
| - | ,max(case t1.stage when 4 then t2.place else null end) | ||
| - | ,max(case t1.stage when 5 then t2.place else null end) | ||
| - | ,max(case t1.stage when 6 then t2.place else null end) | ||
| - | ,max(case t1.stage when 7 then t2.place else null end) | ||
| - | ,max(case t1.stage when 8 then t2.place else null end) | ||
| - | ,max(case t1.stage when 9 then t2.place else null end) | ||
| - | ,max(case t1.stage when 10 then t2.place else null end) | ||
| - | FROM events t1 left outer join places t2 on (t1.place_id=t2.id) WHERE tour_id=227 | ||
| - | -- 会場の出力 | ||
| - | UNION ALL | ||
| - | | ||
| - | ,max(case t1.stage when 2 then t2.venue else null end) | ||
| - | ,max(case t1.stage when 3 then t2.venue else null end) | ||
| - | ,max(case t1.stage when 4 then t2.venue else null end) | ||
| - | ,max(case t1.stage when 5 then t2.venue else null end) | ||
| - | ,max(case t1.stage when 6 then t2.venue else null end) | ||
| - | ,max(case t1.stage when 7 then t2.venue else null end) | ||
| - | ,max(case t1.stage when 8 then t2.venue else null end) | ||
| - | ,max(case t1.stage when 9 then t2.venue else null end) | ||
| - | ,max(case t1.stage when 10 then t2.venue else null end) | ||
| - | FROM events t1 left outer join venues t2 on (t1.venue_id=t2.id) WHERE tour_id=227 | ||
| - | UNION ALL | ||
| - | | ||
| - | -- セットリストの出力 | ||
| - | UNION ALL | ||
| - | | ||
| - | ,s1.col1 | ||
| - | ,s1.col2 | ||
| - | ,s1.col3 | ||
| - | ,s1.col4 | ||
| - | ,s1.col5 | ||
| - | ,s1.col6 | ||
| - | ,s1.col7 | ||
| - | ,s1.col8 | ||
| - | ,s1.col9 | ||
| - | FROM (SELECT max(case t1.stage when 1 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 2 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 3 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 4 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 5 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 6 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 7 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 8 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 9 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 10 then t3.song_title else null end) as " | ||
| - | FROM events t1 left join setlists t2 on (t1.id | ||
| - | left outer join songs t3 on (t2.song_id=t3.id) | ||
| - | WHERE tour_id=227 | ||
| - | AND t2.list_type=1 | ||
| - | GROUP BY t2.order_index | ||
| - | ORDER BY t2.order_index) s1 | ||
| - | UNION ALL | ||
| - | | ||
| - | -- アンコールの出力 | ||
| - | UNION ALL | ||
| - | | ||
| - | ,s1.col1 | ||
| - | ,s1.col2 | ||
| - | ,s1.col3 | ||
| - | ,s1.col4 | ||
| - | ,s1.col5 | ||
| - | ,s1.col6 | ||
| - | ,s1.col7 | ||
| - | ,s1.col8 | ||
| - | ,s1.col9 | ||
| - | FROM (SELECT max(case t1.stage when 1 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 2 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 3 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 4 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 5 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 6 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 7 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 8 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 9 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 10 then t3.song_title else null end) as " | ||
| - | FROM events t1 left join setlists t2 on (t1.id | ||
| - | left outer join songs t3 on (t2.song_id=t3.id) | ||
| - | WHERE tour_id=227 | ||
| - | AND t2.list_type=2 | ||
| - | GROUP BY t2.order_index | ||
| - | ORDER BY t2.order_index) s1 | ||
| - | UNION ALL | ||
| - | | ||
| - | -- Wアンコールの出力 | ||
| - | UNION ALL | ||
| - | | ||
| - | ,s1.col1 | ||
| - | ,s1.col2 | ||
| - | ,s1.col3 | ||
| - | ,s1.col4 | ||
| - | ,s1.col5 | ||
| - | ,s1.col6 | ||
| - | ,s1.col7 | ||
| - | ,s1.col8 | ||
| - | ,s1.col9 | ||
| - | FROM (SELECT max(case t1.stage when 1 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 2 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 3 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 4 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 5 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 6 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 7 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 8 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 9 then t3.song_title else null end) as " | ||
| - | ,max(case t1.stage when 10 then t3.song_title else null end) as " | ||
| - | FROM events t1 left join setlists t2 on (t1.id | ||
| - | left outer join songs t3 on (t2.song_id=t3.id) | ||
| - | WHERE tour_id=227 | ||
| - | AND t2.list_type=3 | ||
| - | GROUP BY t2.order_index | ||
| - | ORDER BY t2.order_index) s1 | ||
| - | $ | ||
| - | </ | ||
| - | |||
| - | 以下のSQL記述、 | ||
| - | < | ||
| - | | ||
| - | </ | ||
| - | のSELECT文でFROMがないのは、不要ならFROM句がいらないPostgreSQLの仕様です。 | ||
| - | |||
| - | Oracleだと FROM DUAL、 | ||
| - | < | ||
| - | | ||
| - | </ | ||
| - | DB2だとFROM SYSIBM.DUALやFROM SYSIBM.SYSDUMMY1でしょうか。 | ||
| - | < | ||
| - | | ||
| - | </ | ||
| - | |||
| - | ==== 生成ロジック ==== | ||
| - | |||
| - | あいかわらず Perl のコードですみません。 | ||
| - | |||
| - | SQLの生成までしかやっていないので、b.plの役割とするならHTML生成ロジックを追加することになります。 | ||
| - | |||
| - | <code perl gensql.pl> | ||
| - | use Encode; | ||
| - | use DBI; | ||
| - | |||
| - | my $dbname=" | ||
| - | my $dbhost=" | ||
| - | my $dbuser=" | ||
| - | my $dbpass=" | ||
| - | |||
| - | my $dbh = DBI-> | ||
| - | |||
| - | my $cgi_id = 227; | ||
| - | |||
| - | my $events_count = 0; | ||
| - | |||
| - | $dbh-> | ||
| - | |||
| - | ## 指定ツアーの公演回数を取得 | ||
| - | my $sth0 = $dbh-> | ||
| - | $sth0-> | ||
| - | $sth0-> | ||
| - | $sth0-> | ||
| - | $sth0-> | ||
| - | $sth0-> | ||
| - | $dbh-> | ||
| - | |||
| - | ## 公演日を横につなげる | ||
| - | my $sql = ' | ||
| - | for(my $cnt=0; $cnt< | ||
| - | { | ||
| - | $sql .=',' | ||
| - | $sql .= sprintf(" | ||
| - | } | ||
| - | $sql .= sprintf(" | ||
| - | |||
| - | ## 開催場所を横につなげる | ||
| - | $sql .= " UNION ALL\n SELECT "; | ||
| - | for(my $cnt=0; $cnt< | ||
| - | { | ||
| - | $sql .=',' | ||
| - | $sql .= sprintf(" | ||
| - | } | ||
| - | $sql .= sprintf(" | ||
| - | |||
| - | ## 会場を横につなげる | ||
| - | $sql .= " UNION ALL\n SELECT "; | ||
| - | for(my $cnt=0; $cnt< | ||
| - | { | ||
| - | $sql .=',' | ||
| - | $sql .= sprintf(" | ||
| - | } | ||
| - | $sql .= sprintf(" | ||
| - | |||
| - | ## 区切り | ||
| - | $sql .= " UNION ALL\n SELECT "; | ||
| - | for(my $cnt=0; $cnt< | ||
| - | { | ||
| - | $sql .=',' | ||
| - | $sql .= "' | ||
| - | } | ||
| - | $sql .=" | ||
| - | |||
| - | ## セットリストを横につなげる | ||
| - | $sql .= " UNION ALL\n SELECT "; | ||
| - | my $sql2 = " | ||
| - | for(my $cnt=0; $cnt< | ||
| - | { | ||
| - | $sql2 .=',' | ||
| - | $sql2 .= sprintf(" | ||
| - | |||
| - | $sql .=',' | ||
| - | $sql .= sprintf(" | ||
| - | } | ||
| - | $sql2 .= sprintf(" | ||
| - | left outer join songs t3 on (t2.song_id = t3.id) | ||
| - | WHERE tour_id=%d | ||
| - | AND t2.list_type=1 | ||
| - | GROUP BY t2.order_index | ||
| - | ORDER BY t2.order_index", | ||
| - | $sql .= " FROM (" . $sql2 . ") s1\n"; | ||
| - | |||
| - | ## 区切り | ||
| - | $sql .= " UNION ALL\n SELECT "; | ||
| - | for(my $cnt=0; $cnt< | ||
| - | { | ||
| - | $sql .=',' | ||
| - | $sql .= "' | ||
| - | } | ||
| - | $sql .=" | ||
| - | |||
| - | ## アンコールの演目を横につなげる | ||
| - | $sql .= " UNION ALL\n SELECT "; | ||
| - | $sql2 = " | ||
| - | for(my $cnt=0; $cnt< | ||
| - | { | ||
| - | $sql2 .=',' | ||
| - | $sql2 .= sprintf(" | ||
| - | |||
| - | $sql .=',' | ||
| - | $sql .= sprintf(" | ||
| - | } | ||
| - | $sql2 .= sprintf(" | ||
| - | left outer join songs t3 on (t2.song_id = t3.id) | ||
| - | WHERE tour_id=%d | ||
| - | AND t2.list_type=2 | ||
| - | GROUP BY t2.order_index | ||
| - | ORDER BY t2.order_index", | ||
| - | $sql .= " FROM (" . $sql2 . ") s1\n"; | ||
| - | |||
| - | ## 区切り | ||
| - | $sql .= " UNION ALL\n SELECT "; | ||
| - | for(my $cnt=0; $cnt< | ||
| - | { | ||
| - | $sql .=',' | ||
| - | $sql .= "' | ||
| - | } | ||
| - | $sql .=" | ||
| - | |||
| - | ## Wアンコールの演目を横に並べる | ||
| - | $sql .= " UNION ALL\n SELECT "; | ||
| - | $sql2 = " | ||
| - | for(my $cnt=0; $cnt< | ||
| - | { | ||
| - | $sql2 .=',' | ||
| - | $sql2 .= sprintf(" | ||
| - | |||
| - | $sql .=',' | ||
| - | $sql .= sprintf(" | ||
| - | } | ||
| - | $sql2 .= sprintf(" | ||
| - | left outer join songs t3 on (t2.song_id = t3.id) | ||
| - | WHERE tour_id=%d | ||
| - | AND t2.list_type=3 | ||
| - | GROUP BY t2.order_index | ||
| - | ORDER BY t2.order_index", | ||
| - | $sql .= " FROM (" . $sql2 . ") s1\n"; | ||
| - | |||
| - | print $sql; | ||
| - | |||
| - | $dbh-> | ||
| - | </ | ||
| - | |||
| - | ==== gensql.plのちょっとした説明 ==== | ||
| - | |||
| - | やっていることは単純明快で、 | ||
| - | |||
| - | * 横に並べる項目数を求める | ||
| - | * 公演日を横に並べるSQL生成 | ||
| - | * 開催場所を横に並べるSQL生成 | ||
| - | * 会場を横に並べるSQL生成 | ||
| - | * セットリストを横に並べるSQL生成 | ||
| - | * アンコール演目を横に並べるSQL生成 | ||
| - | * Wアンコール演目を横に並べるSQL生成 | ||
| - | |||
| - | となっています。最初の項目数を求めたら、必要な項目数だけ追加したSQLを生成し、各SQLをUNION演算子で結合し1本のSQLに合成します。\\ | ||
| - | このSQLの実行結果はあたかもSELECTの結果を縦につないだかのように見えます。 | ||
| - | |||
| - | === 項目数を求める === | ||
| - | |||
| - | 最初に横に並べなきゃいけない項目数をSQLで求めています。\\ | ||
| - | 現在のデータだと、横に展開する時に使う項目stageの最大値と同じになるはずです。 | ||
| - | <code perl> | ||
| - | ## 指定ツアーの公演回数を取得 | ||
| - | my $sth0 = $dbh-> | ||
| - | $sth0-> | ||
| - | $sth0-> | ||
| - | $sth0-> | ||
| - | $sth0-> | ||
| - | $sth0-> | ||
| - | $dbh-> | ||
| - | </ | ||
| - | |||
| - | === 1行出力のSQL生成 === | ||
| - | |||
| - | 出力項目を、求めた個数回だけ生成します。\\ | ||
| - | 例えば公演日抽出SQLの生成処理、 | ||
| - | <code perl> | ||
| - | ## 公演日を横につなげる | ||
| - | my $sql = ' | ||
| - | for(my $cnt=0; $cnt< | ||
| - | { | ||
| - | $sql .=',' | ||
| - | $sql .= sprintf(" | ||
| - | } | ||
| - | $sql .= sprintf(" | ||
| - | </ | ||
| - | の部分で、以下の選択項目が$events_count個生成されることになります。※MM, | ||
| - | |||
| - | <code sql> | ||
| - | cast(max(case stage when MM then event_date else null end) as varchar) as " | ||
| - | </ | ||
| - | |||
| - | 他の部分も基本は同じです。 | ||
| - | |||
| - | === 複数行の発生するSQL生成 === | ||
| - | |||
| - | セットリスト、アンコール演目、Wアンコール演目の処理では、サブクエリをつかうSQLを生成しています。\\ | ||
| - | 以下はセットリストのSQL生成処理です。 | ||
| - | <code perl> | ||
| - | ## セットリストを横につなげる | ||
| - | $sql .= " UNION ALL\n SELECT "; | ||
| - | my $sql2 = " | ||
| - | for(my $cnt=0; $cnt< | ||
| - | { | ||
| - | $sql2 .=',' | ||
| - | $sql2 .= sprintf(" | ||
| - | |||
| - | $sql .=',' | ||
| - | $sql .= sprintf(" | ||
| - | } | ||
| - | $sql2 .= sprintf(" | ||
| - | left outer join songs t3 on (t2.song_id=t3.id) | ||
| - | WHERE tour_id=%d | ||
| - | AND t2.list_type=1 | ||
| - | GROUP BY t2.order_index | ||
| - | ORDER BY t2.order_index", | ||
| - | $sql .= " FROM (" . $sql2 . ") s1\n"; | ||
| - | </ | ||
| - | 二つのSELECT文が生成されているのがわかると思います。これは、セットリストが公演日や公演場所の生成と異なり複数行生成される必要があるためです。 | ||
| - | サブクエリでセットリストの演目順序でソートされたレコードセットを生成しています。このレコードセットをこの順番で適用します。 | ||
| - | |||
| - | {{tag> | ||
documents/database/sql-0007.txt · 最終更新: by k896951
