【 ビジネス奮戦記 】

その他の部屋へ戻る



<<< 久しぶりの大長編 >>>

人には分け隔てなく思考する頭脳というものが与えられているわけですが、
頭脳明晰、いわゆる頭の良い人と言われる人は、
そもそも先天的に、例えるならサラブレットのように、
血統的、遺伝的に、優れた親、先祖から生まれた純血種だけでしょうか?
先天的な素質を持っている人だけが、優れた頭脳を持つのでしょうか?

もちろん応えは否であることは全員知っています。
何故なら後天的に発達していく人が数多くいるからです。
もちろん偉人といわれる偉大な人達の多くが、
何らかの優秀種的な家系から排出されている割合が多いことも知っています。
しかし、優秀な家系から誕生した人が破滅的・自堕落的な人生を歩む事例もあるし、
逆に、恵まれない家庭環境から優れた人物になる人もいます。

或る人は政治にその持てる思考力を注ぎ、
或る人は科学研究にその持てる思考力を注ぎ、
或る人は商売にその持てる思考力を注ぎ、
或る人はバクチ・ギャンブルにその持てる思考力を注ぎ、
或る人は物作りにその持てる思考力を注ぎ、
或る人は美味なる味作りにその持てる思考力を注ぎ、
或る人は自分の容姿を美しくすることにその持てる思考力を注ぎ、
或る人は演劇表現にその持てる思考力を注ぎ、
或る人は効率よく人を殺傷することにその持てる思考力を注いだりするわけです。

地球上の生物で単一種として最も繁栄を極めている人間が、
全てにおいて頭脳明敏・明晰でないのは何故でしょうか?

ある人が成長していく上で、どういう環境・境遇で育ち、
何を経験したか、というのが結構重要なファクターであることは明白で、
そこに、その人が遺伝体質的に持っていた素質、例えば性格・気質が作用して、
その後のその人間の歩む軌跡を決めていくというのが大筋のような気がします。

そしてそれらの作用条件があまりにも複雑であるため、
一言で言い表すことが出来ないので、「運命」という言葉で片付けることがあります。
「運命」という言葉に対して反感を抱いているわけではありませんが、
普通一般に用いるのには便利な表現であると思います。

とまぁ、そんなことは比較人類学者でもなんでもないキクメンが、
書いたところで、というよりもそんな誰もが分かりきったことを、
ことさら格好つけて書いて恥ずかしくないのか状態なのですが、
最近50代の部長から借りた本が面白くて、
なんとなくそういうことに思いを馳せてしまったので、
つらつらと書いてしまったのです。

ちなみにその本は、もしかしたら、世界ではじめて、
かのライト兄弟を抜いて、人類発の飛行機を発明した人になれたかもしれない、
一人の日本人の話であります。(多分実在の人)

前置きでもなんでもない前置きを書いたところで、本日の主題へ移ろうと思います。
本日の主題はロジックパズルであります。

(ここから下は、SQLとかプログラムに興味のない人は読み飛ばしましょう)

<<< たいしたことはないロジックパズル >>>

さて、平平凡凡な凡人のトップバッター、
即効空振り三振バッターアウッ!のキクメンは、
相変わらずつまらん才能の持ち主であることは全宇宙知的生物にとっての周知の事実なわけですが、
( なんだとう! )
最近は仕事でほとんどプログラムを書いてなかったところで、
久しぶりに短期プロジェクトのメンバーとして、
久しぶりにプログラムを書くことになりました。

久しぶりにプログラムを書いていると、楽しいです。
( くそう・・なんか誰かにハメられているようでヤだな )

そこで、よくある事例のロジックを書くことになりました。

まずは結果画面から紹介しますと、

分類コード補助分類コード分類名称補助分類名称複数派生型アリ
00101ザク地上型アリ
00102ザク宇宙型
00103ザク宇宙・高機動型
00104ザク海中型
00201ドム地上型アリ
00202ドム宇宙型
00399ゲルググ万能型ナシ
00405ズゴック水陸両用型ナシ
00577アッグ嬉しそうな人ナシ


というような画面で、
特に 「 複数派生型アリ 」 という項目の部分に着目して下さい。

次にデータベースのテーブルを紹介しますと、
TABLE名 : MSMST(モビルスーツマスタテーブル)
項目名 : BUNRUICD(分類コード)
HOJOBUNRUICD(補助分類コード)
BUNRUIMEI(分類名称)
HOJOBUNRUIMEI(補助分類名称)


というような簡単ないわゆるマスタテーブルです。
複数派生型 という項目はテーブルの項目にはなく、
プログラムの方で判断してアリかナシを付加します。

上のHTMLを見れば明らかですが、一応テーブルに入っているデータも書いてみましょう。

00101ザク地上型
00102ザク宇宙型
00103ザク宇宙・高機動型
00104ザク海中型
00201ドム地上型
00202ドム宇宙型
00399ゲルググ万能型
00405ズゴック水陸両用型
00577アッグ嬉しそうな人


ではこれらを元に、上のHTMLを 動的に 作り出すロジックを考えましょう。

ここでのキモは上にも書いた「複数派生型」 という項目で、
ここは TD タグで ROWSPAN の制御をしなければならない という点です。

分類名が ザク で、補助分類名が 地上型 の行だけ、

<td nowrap align=center rowspan=4>

と書かなければならないのがロジックのミソです。
( その下のザク宇宙型からは次のドムになるまで TD タグ自体書いてはいけない

ちなみこういうケースはかなりしょっちゅう表れます。

大抵の場合は、普通にSELECT文、
select BUNRUICD, HOJOBUNRUICD, BUNRUIMEI, HOJOBUNRUIMEI
from MSMST
order by BUNRUICD, HOJOBUNRUICD;
を書いてループ処理の中で、分類コードを変数に格納して判断すると思います。

もう少し詳しく書くと、
上記 SQL 実行

<フェッチのループ内処理>
ループ1回目処理
部門コードを変数に格納(変数名は部門コード)
ループ2回目以降処理
フェッチした部門コードと前部門コードを比較して、
等しい場合はROWSPAN用のカウントをインクリメントする。
違う場合は変数、前部門コードに今フェッチした部門コードを代入する。
さらにROWSPAN用のカウンタを用いて、
HTML(TABLE タグの TD 属性、 ROWSPAN 指定付き)を作成する。
部門が何件あるかは、例えばザクならば、次の部門のドムがくるまで分からないので、
作成したHTMLはこの時点では書き込めなく、
全てバッファ領域に格納する。
しかし、問題点として、テーブル内に膨大な量のデータが入っていた場合、
バッファオーバーフローが発生する可能性がある。
<フェッチのループ内処理ここまで>

最終行のデータが、直前の部門と違う部門の場合と、
最終行のデータが、直前の部門と同じ部門の場合とでHTMLは違うので、
それを判断して書き分ける。

最後に一括してバッファ内に格納したHTMLを書き出す。

これでいいでしょうか?
ハイそうですね、答えは ダメ です。

ROWSPAN 属性の部分は、その項目の最上段に書かなければならないからです。

上の例だと、

<tr><td nowrap>ザク</td>中略</tr>
<tr><td nowrap>ザク</td>中略</tr>
<tr><td nowrap>ザク</td>中略</tr>
<tr><td nowrap>ザク</td>中略<td nowrap rowspan=4>アリ</td></tr>

という風になってしまって、おかしい表示になってしまいます。
正しくは、

<tr><td nowrap>ザク</td>中略<td nowrap rowspan=4>アリ</td></tr>
<tr><td nowrap>ザク</td>中略</tr>
<tr><td nowrap>ザク</td>中略</tr>
<tr><td nowrap>ザク</td>中略</tr>

としなければなりません。

これを実装するとなると、フェッチ時のループ処理一つでは足りなく、
さらにループ処理を増やして、
なおかつ、配列変数(言語によっては連想記憶配列なども使う)を使用してロジックを組む必要があり、
そうなるとパッと見で誰でもすぐに理解できる簡単なロジックではなくなってしまいます。

CPUやメモリのリソースも喰うし、そうなるとデータ件数が多いときのパフォーマンスにも影響するし、
複雑なロジックはそれだけバグの元だし、プログラム自体のメンテナンスの管理機能も良くないです。

なによりプログラムを書く本人が、
そんなロジックを考えて、書いて、しかも動作テストすること自体が面倒くさいです。

そこで、良い意味での手抜き者は、
手を抜きたい、ラクをしたい、もっとシンプルに、
フェッチ時のループ内でその時その行だけを判断してそのままHTMLを書き出せれば最高なのになぁと思うわけです。

そこで、SQLの知識とSQLパズルを解く能力がある人は、
SQLを工夫して、ループ処理一発で、
しかもその行だけのシンプルな IF 文でHTMLを書き出そうとするための脳内パズルクイズの旅に出かけるのです。

さて、そこで真っ先に浮かぶのが、まず ROWSPAN で使用する数字を SQLの COUNT 文で取れないかということです。
つまり部門コードでグループ関数( GROUP BY )を使用して、ジョインすれば、
その部門の出現回数があらかじめSQLで取れるから、
プログラムロジックのループ処理の中でカウントしなくてもいいということです。

で、 SQL を書いてみるとこうなります。

select tab1.BUNRUICD, tab1.HOJOBUNRUICD, BUNRUIMEI, HOJOBUNRUIMEI,
tab2.ROWSPAN
from MSMST tab1,
( select BUNRUICD, count(BUNRUICD) ROWSPAN
from MSMST
group by BUNRUICD) tab2
where tab1.BUNRUICD = tab2.BUNRUICD
order by    tab1.BUNRUICD, tab1.HOJOBUNRUICD;

で、上の SQL の実行結果は、

00101ザク地上型4
00102ザク宇宙型4
00103ザク宇宙・高機動型4
00104ザク海中型4
00201ドム地上型2
00202ドム宇宙型2
00399ゲルググ万能型1
00405ズゴック水陸両用型1
00577アッグ嬉しそうな人1

と、なります。
これで ROWSPAN = "数字" の 数字の部分に関してはクリア出来たことになります。

ここまでは、まぁ普通にたどりつくわけですが、
問題はこの先、例えば上の例だと、2レコード目の ザク からは、
次の ドム が出てくるまで TD タグを書きたくないというところです。

ここで結局、ループ処理内部で、
部門コードを新旧を比較しあって変わるまでは TD を書かないなどというロジックを入れるのでは、
結局あまりロジックはシンプルにはなりません。

まさしくそこがハードルなのです。

最初に1レコード目のザク(地上型)をフェッチしたときに、
「キクメン君、これは TD を書いてもいいよー!」と教えてくれると最高なのです。
で、次の2レコード目のザク(宇宙型)をフェッチした時は、
「キクメン君、これは TD を書いちゃダメだよー!」と教えてくれると最高なのです。
でもって、5レコード目のドム(地上型)をフェッチした時は、
「キクメン君、これは TD を書いてもいいよー!」というように、以下繰り返しで教えてくれると最高なのです。

つまりこれを頭の中で、表として描いてみると、

00101ザク地上型4TD書いてもいいよ!
00102ザク宇宙型4TD書いちゃダメだよ!
00103ザク宇宙・高機動型4TD書いちゃダメだよ!
00104ザク海中型4TD書いちゃダメだよ!
00201ドム地上型2TD書いてもいいよ!
00202ドム宇宙型2TD書いちゃダメだよ!
00399ゲルググ万能型1TD書いてもいいよ!
00405ズゴック水陸両用型1TD書いてもいいよ!
00577アッグ嬉しそうな人1TD書いてもいいよ!


とまぁ、こういう具合にSQLの実行結果が返ってくると、
フェッチ時のループ処理一発でHTMLがそのままその1行で出力出来て、
アヒョー!最高ー!ということになるわけです。

まず、この表が頭に思い描けるかどうかが、運命の分かれ道なのです。
(御免、そこまで大それたもんじゃないね)

で、そうなると、今度は SQL のスキルがそこそこある人だと、
架空の項目とアウタージョイン(外部結合)を用いてやれば出来そうじゃん、というトコに行き着きます。

で、もう、いきなり結論から書くと、SQL は以下のようになります。

select tab1.BUNRUICD, tab1.HOJOBUNRUICD, BUNRUIMEI, HOJOBUNRUIMEI,
tab2.ROWSPAN, nvl( tab3.TD_FLG, 0 ) TD_FLG
from MSMST tab1,
( select BUNRUICD, count(BUNRUICD) ROWSPAN
from MSMST
group by BUNRUICD) tab2,
(select BUNRUICD, min(HOJOBUNRUICD) HOJOBUNRUICD, 1 TD_FLG
from MSMST
group by BUNRUICD) tab3
where tab1.BUNRUICD = tab2.BUNRUICD
and tab1.BUNRUICD = tab3.BUNRUICD(+)
and tab1.HOJOBUNRUICD = tab3.HOJOBUNRUICD(+)
order by    tab1.BUNRUICD, tab1.HOJOBUNRUICD

(上の SQL はオラクルに準じた書き方、とりわけアウタージョイン(外部結合)の部分、
(+) の個所は、他の DB の場合は LEFT JOIN ON ムニャムニャ・・ とか書く)

で、上の実行結果は下のようになります。

BUNRUICDHOJOBUNRUICDBUNRUIMEIHOJOBUNRUIMEIROWSPANTD_FLG
00101ザク地上型41
00102ザク宇宙型40
00103ザク宇宙・高機動型40
00104ザク海中型40
00201ドム地上型21
00202ドム宇宙型20
00399ゲルググ万能型11
00405ズゴック水陸両用型11
00577アッグ嬉しそうな人11


どうでしょう、ここまできたら、もうコッチのもんよ!状態になったと思います。
そしたら、あとはもう煮るなり焼くなりなんでもOKです。

じゃぁプログラムロジックでも書きましょうか。
とりあえずHTMLを書き出すキモの部分、ループロジック部を書いてみましょう。
( 言語は Perl ね )

  # SQL 実行結果を HTML に埋め込んで書出し
  while( @fetched = $sth0->fetchrow ){
    ($bunruicd, $hojobunruicd, $bunruiname, $hojobunruiname,
     $rowspan, $td_flg ) = @fetched;

    print "  <tr>\n";
    print "  <td nowrap align=\"center\">$bunruicd</td>\n";
    print "  <td nowrap align=\"center\">$hojobunruicd</td>\n";
    print "  <td nowrap>$bunruiname</td>\n";
    print "  <td nowrap>$hojobunruiname</td>\n";
    if($td_flg == 1){
      print "  <td nowrap align=\"center\" rowspan=\"$rowspan\">";
      if($rowspan > 1){
        print "アリ";
      }else{
        print "ナシ";
      }
      print "</td>\n";
    }
    print "</tr>\n";
  }

たったこれだけです。
SQL 文はちょっと長くなったけど、
ロジック部は思いっきり単純で短くなったと思います。

これで、最初に書きたい HTML は実現できます。

ここで、ちょっと突っ込みたい人だと、
上の場合はマスタテーブルなので、そんなに件数は入らないけれども、
業務トランザクションデータの格納テーブルなど、
データ件数がすごく多い場合の SQL のパフォーマンスはどうなのと考えると思います。

確かにサブクエリー(副問合せ)の数分だけ、
上記の場合だとTABLE ACCESS は FULL になりますが、ちょっとまってください。
それよりも配列格納型の場合バッファ領域をどんどん食い潰していくわけでそっちの方が心配です。
CやCプラの場合、予めアロケートするのでしょうけど、
アロケートサイズをオーバーしたら、かなり最悪な事態になります。
また、JAVAの場合は最悪ガベージコレクションが発動するかもしれません。
VB系で ReDIM する場合も然りです。

しかもプログラムはなんだかしち面倒くさいロジックで、
CPU やメモリリソースも喰うわけで、そもそもその場合、
WEB システムで全件 HTML 表示しちゃあかんだろということになります。

その場合 WHERE 句で絞込みするか、
予め表示件数を20件などに制限しますが、
それでも、配列格納、HTML も文字列バッファに格納作戦だと、
やっぱり見づらく、管理も大変な、そもそもシンプルじゃないソースで、
あまり好ましくないなぁと(超個人的に)思うのです。

そういうわけで、SQL を工夫して、
フェッチループ内一発でHTMLを書き出すように作ってみました。

と、まぁ、ここまで壮大に書かなくてもいいレベルのロジックパズルを書いてしまいましたが、
それもこれも、これまで自分がアチコチの現場(様々なプログラム言語、開発環境)に行った時に、
上記のようなケースの HTML 表示ロジックを具現化するのに、
ほぼ行った先の100%が、配列と複数ループ(酷い場合はループの入れ子が複数!)
でやっていたので、あまり頭の良くない(しかも集中力も長続きしない)キクメンは、
パッと見ただけで、「あーよー分からん、見るのもヤだ」という具合になったので、
一応ちょっとでもシンプルなロジックで作成して、
見やすいソースを増やして欲しいキャンペーンの一環として、
仕事中に長々ダラダラと書きました。

キクメンが仕事もせずに、こんなしょうもないことを書いている間、
真面目に仕事していた仲間の皆、スマンチョ。



その他の部屋へ戻る