2007年11月10日

Cheetan->CakePHP->CodeIgniter

前回はPHPのフレームワークが乱立しすぎていて、どれに絞ったらよいか迷いながら、適当にチュートリアル(のコピペ)とかしつつ、結局「ちいたん」という軽量のフレームワークに辿りついたところまで書きました。

ただこの時点では(まあ今もそうですが)フレームワークの良し悪しを判断することなどできず、単にチュートリアルが動いたっていうレベルなんですが…。でも一応動いたりするとその後の学習意欲が全然違うわけで、とりあえずちいたんをごにょごにょいじったりしてみました。

CakePHPも今度は多少真面目にチュートリアルに取り組み、Bakeで自動生成したりとかアクティブレコードでHABTM関連のテーブルを操作?してみたりとか。CodeIgniterもビデオチュートリアルを見ながら、ちゃんと一時停止して全部コード書いて〜みたいなことをやってみました。

んで、そうこうするうちに要するに基本がわかってない、というかそもそもオブジェクト指向だとかMVCとかいう以前に、自分が何をやりたいのかを見失ってることに気づきました(遅い)。

LIMITを使ったページングを実装したい、というのはこれまでで書いてきたことですが、そもそもそれを実装しようにも元のスクリプトがぐちゃぐちゃで、そこをすっきりさせたいがためのフレームワークだったわけです。

ですが当たり前のことですが、フレームワークを使えば自動的にぐちゃぐちゃなコードがすっきりするわけでもなく、一体どこが問題なのか?という部分を考えていくうちに、要するに表示系のコードとデータベースからデータを取得しているような部分のコード、またFORMからの入力を整形?しているような部分のコードなどを分離すればいいのではないか?と気づきました。

というかMVCに基づいたフレームワークで0から構築していけば、まあよっぽどおかしな事をしなければ大抵はそうなるようにできているわけです。そこで逆にフレームワークなどを使わなくても単純に今のコードを切り分けちゃえばいいのではないか?ということに思い至りました。

きっかけは、そんな頃にたまたま目にしたsymfonyのドキュメントがまさに目から鱗というのか「なんだ、こんな単純なことでいいのか」という実感を与えてくれました。

要するに切り分けたい部分のコードを別ファイルにしてincludeっつーかrequireしちゃえ、というあまりにも単純な話なんですが、フレームワークに拘っていたというか、囚われていた時にはなかなか気づかない部分だったわけです。

そういう心境になると不思議なもので見えてなかったものが見えてくるというか、別にちいたんでもCakeでもCodeIgniterでも、またはフレームワークを使用しなくてもなんとなくできそうな気分になってきました。

最終的にCodeIgniterを選択したわけですが、Cakeと比較するとModelを特に作らなくてもよいという部分がポイント高かったなぁと、ちいたんと比較するとドキュメントの充実度とかライブラリやヘルパーの類の多さが決め手だったかなとおもいます。

その他ZendとかPradoとかいろいろありましたが、別にフレームワーク使わなくてもいいや的な目(どんな目だよ)で見ると、CodeIgniterが一番しっくりきた、といったところでしょうか。

そんなわけでようやく次回からCodeIgniterでの構築記になる予定です。
posted by ciallost at 02:26| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2007年11月09日

PHPのフレームワーク大杉

前回まででようやくページングの目途がついたので今回からやっとフレームワークの話を書こうかとおもいます。その前にここまでのまとめというかそもそもなんでフレームワークなの?というあたりをもう一度確認してみると…。

発端としては、まず従来の手続き型で書いたぐちゃぐちゃの検索アプリスクリプトがあり、そのスクリプトでは検索データ全件取得&スクリプトで対応方式のページングが実装されていました。

ですがデータ件数が増えるに従いどうにも我慢ならないほど遅くなってきたので、とりあえずSQLのLIMITを使って表示データだけ取ってこれないか?という考えにたどり着きました。

そして試行錯誤の上SQL一発でなんとか表示データだけを取ってくることに成功し、また速度的にも全件取得よりはマシだろうということが判明しました。

んで、そのSQLを実装(SQLを実装?ってなんか変かな?)すればいいのですが、最初に述べた通りあまりにもぐちゃぐちゃで、もはや新機能を付け加えることはおろか、どこか一箇所でもいじろうものならわけがわからなくなること確実なので、せっかくのSQLを実装しようにもこのままじゃ手がつけられない状態になっているわけです。

そこで以前から耳にしていたフレームワークなるものを使えば、もしかしたらこの泥沼的状況から抜け出せるのではないか?…とまあ前回までのあらすじはこんなところです。

んで、PHPのフレームワークを探したわけですが、これが出るわ出るわ!あまりトリッキーっつかマニアックなことをしたいわけでもなく、当然日本語のドキュメントも多いほうがいいわけで、まあデファクトスタンダード的なヤツを使っとけば間違いないだろう…ぐらいに考えていたのですが、探してみてビックリ!もう乱立状態どころかこれだ!といったものが見つからない。

最初に引っかかったのがsymfony。RoRの流れを汲み〜とかMojaviとかいうフレームワークから派生してとかなんとか。でも重いとかもっさりしてるとか個人用というよりエンタープライズ向け?みたいなあまり言い噂を聞かないので却下。

その次に引っかかったのがMojavi。これはなんか日本のユーザは多いらしい。けどRoR以前なのでちょっと1世代前的な感は否めない…とかなんとか。RoRってなんぞ?ま、いいや、とにかく古いらしいから却下。

その後MapleだのEthnaだのチラ見して(謎)たどり着いたのがCakePHP。これはRoRの流れを汲むPHPのフレームワークで悪痴ぶれこおどにも対応してるとかなんとかかんとか…。だからRoRってなんぞや?アクチベーションってWindowsXPの悪名高いコピープロテクトでしょ?…な状態の私。

で、まずRoRを調べてみたらRuby on Railsの略と判明。あ〜なんかちょっと前に盛り上がってましたっけ?的な知識しかない(注:知識ではない)。とにかくおそろしく簡単にデータベースを使ったWEBアプリができてしまう。コードすら書く必要もない場合もある。みたいな(--;

でCakePHPはそれのPHP版的な位置付けか?ま、それはとりあえずわかったから置いといて、ActiveRecordってのはなんなんだ?

ネットにはいろいろ書いてあるけどなかなか実態が掴めない。O/Rマッパっていったい?らっぱクラス?汗くさメソッド?なんのこっちゃ…。わからな杉。

でもわからないなりにもなんとなくCakePHPよさげ。そんでチュートリアルをやってみる。やってみるといっても単にコピペしてファイル作ってるだけ。これでわかるようになるわけがない。ってか動かない。とりあえず挫折。

そしてまたフレームワーク探しの旅へ…。CodeInteger?整数しか書けないフレームワークか?ナワケナイ。あ〜CodeIgniterか。いぐにてってなんだ?ふむふむ点火器ね、イグナイタって読むのか。なるほどなるほど。

で、チュートリアルはどこぞ?ん、ない。代わりにビデオ。ほーほー。ふむふむ。なるほど。とりあえず見ただけで挫折。

次。(次って…)ちいたん。軽いらしい。AJAXにも対応。ほーほーなかなかよさげじゃん。ネーミングは奥さんの愛s…ry。あう、おえ、いお。ちょっとキモイ。でもめげずに(なんにだ?)チュートリアル。またまたコピペ。んで挫s…おおおおお!動いた!なんでだ?さっぱりわからん。

あwせdrftgyふじこlp;@
ラベル:PHP framework
posted by ciallost at 22:49| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2007年11月08日

JOIN句?VIEW?GROUP?

前回、我流の変なSQLを晒しましたが、果たしてこれで当初の全件取得した上でスクリプト上でページングする方法よりも速度的に改善されているのか?という疑問がありました。

そこで簡単に速度比較をしてみようとおもい、実はその時点で今使用しているサンプルのデータベースを作ったのですが、とりあえず全件取得方式と前回のSQL方式とで比較してみました。

といっても素人なので(笑)ちゃんとした?ベンチマークの取り方とかよくわからずになんとなくスクリプトの先頭付近の時間と最後の付近の時間の差を取ってみました。

結果は我流SQLの圧勝でした!(^^)v まあ元々の全件取得方式が何秒単位(5秒とか)という検索アプリとしては致命的なくらいの遅さだったのでナンですが、まあとりあえずコンマ何秒、場合によっては小数点第二位まで0があったので個人的にはヨシとしました。

但しあくまでも全件取得方式と比較してでの話で、採用しなかった「カウント方式」や「SQL多発方式」とは比べていません。なのでもしかしたらその方が速いかもしれません。おいおい暇があったら計ってみるかもしれませんが。

で、今回はこの我流SQLをちょっと手直ししてみました。といっても速度改善とか“ぱふぉおまんすちゅうにんぐ”とかいう高尚な作業ではなく、単に自分で気になってた部分を書き換えただけなんですが…。

まずJOINですがモノの本(謎)によるとJOIN *** ON *** とかINNER JOINとかレフト?とか“あうたぁ?”とかなんとかいろいろ書いてあるわけです。自分はプログラムはもちろんSQL(っつかDB)も素人なので今までWHERE句でキーを連結?していて「あ〜これで連結すんだな」程度にしか考えていなかったのですが、どうもWHERE句というのは本来はフィルタ?というか絞込み?というのか要するにちゃんとした?本来の?検索条件を書くための場所のような感じで、JOINの条件はON以降に書くのがなんとなく正統派のような気がしました。(?が多いな)

んで、まあWHERE句でやってるJOINは普通の?っつーか一番一般的?っぽいINNER JOINということらしく、さらにINNER JOINの場合は特にINNERとかつけなくても単にJOINでいいらしく、さらに条件はONの後に今までWHERE句に書いてあったことを書けばいいらしい…ということが判明したのでそのように書き換えてみました。
FROM newcity n
JOIN joined j ON n.id = j.newcity_id
JOIN oldcity o ON o.id = j.oldcity_id
JOIN pref p ON p.id = n.pref_id

サブクエリのFROM句のみですがだいたいこんな感じです。やってることは同じなんですがこっちのほうがすっきりするというか、ぱっと見何と何のテーブルを連結しているのかがわかりやすいかなぁ?と。

んでここまで書いてみて(SQLを)フとおもったのが「毎回毎回JOINするんかぃ?(何故か関西弁チック)」ということで、これもモノの本によるとVIEWという機能があるらしい。しかもMySQLでもVer.5以上だとサポートされてる云々…。

というわけでVIEWについて調べてみる。すると要するにクエリーに名前を付けて保存してるだけだ…と、んで使うときは既存のテーブルみたいに扱える…と、でも実際そういうテーブルが存在するわけではないので正規化が崩れるようなこともない…と、スクリプトはすっきりする…と。

とまあ以上のようにいいことづくしらしいので即採用決定
CREATE VIEW city AS
SELECT
p.name pref,
n.id nid,
n.city nc,
yomi,
o.id oid,
o.city oc,
gdate
FROM newcities n
JOIN newcities_oldcities j ON n.id = j.newcity_id
JOIN oldcities o ON o.id = j.oldcity_id
JOIN prefs p ON n.prefs_id = p.id

で、めでたくVIEWができたのでSQLをVIEWを使って書き直し。
SELECT
nid,
pref,
nc,
yomi,
gdate,
id oid,
city oc
FROM
(SELECT DISTINCT
nid,
pref,
nc,
yomi,
gdate
FROM city
WHERE some_condition
LIMIT offset, limit
) AS vn,
oldcities o,
joined j
WHERE vn.nid = j.newcity_id
AND o.id = j.oldcity_id

外側のJOINはWHERE句になったままですが、ま、いいや(^^;

んで一応これで動くことは動くんですが、このSQLが果たして“ちゃんとした?”モノなのかどうか結構不安だったのでネットをごそごそと調べてみると…。

まずFROM句のサブクエリに関してはMySQLのマニュアルでも“正式に使用できます”と書いてあったので一応ひと安心。

次にDISTINCTですが別に使用法は間違ってなさそうなのですが、ネットを検索するとDISTINCTは遅いとかGROUP BYに変えろとかいう意見もちらほらと。ですが最終的にDISTINCTとGROUP BY使いわけについてというスレッドを見て、DISTINCTのままいこうと決めました。

上記のスレッドではある方が、DISTINCTは遅いのでGROUP BYにすべきだ!という間違った考え方が流布されているようだが、そんなことはないということを力説されています。またこちらのBlogでもGROUP BYの方が多少は速いかもしれないが可読性の面からも重複行を削除する場合はDISTINCTの方がよいのではないか?とおっしゃられています。

というわけでSQLもすっきりしたので、次回ようやくフレームワークの話題へ…。(ってかいつになったらCodeIgniterの話書けるんだろう)
ラベル:SQL
posted by ciallost at 16:05| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2007年11月07日

HABTMをSQL一発でページングする方法

前回、全件取得しないでページングするためにはどうすればいいか?ということで某氏に伝授された2つの方法を紹介しましたが、どちらもイマイチしっくりこないという個人的なワガママにより却下されました。

で、最後に「本当に旅に出る〜」と書きましたが、実際この後旅に出ました。実はこのBlogの最初の10記事ぐらいまでの部分はリアルタイムではなく過去のメモ?です。本当は早くCodeIgniterのことを書きたいのですが、それを書くためにはどうしても何を作ってるかぐらい説明しておかなければならずこのようなことになっています。

それはともかく旅先でもいろいろ考えていました。おもうに素人ってのは実利よりも体裁の方を重要視する傾向にあるのではないか?(オレダケカ)と。

というのも前述のカウントする方法やSQL多発などは素人目にはどうもしっくりこないわけです。おそらくプロの方はプログラムの最終的な形?というか機械語レベルに翻訳されて実行されるところまでを即座にイメージできているのではないか?とおもうのです。

ですからループの中でカウントしようとSQLを多発しようと、一見素人目には「なんてめんどくさい処理を行ってるんだ?」とか「どーみても力技だろ?」とか見えるようなことも、「最終的には同じことでしょ?」みたいな感じであっさりと書けてしまうのではないかと?

ところが素人は悲しいかなそうした低レベルの処理のことまで頭が回りませんからループの中で自力でカウントしたり、SQLをデータ件数分投げたりとかいうことになると生理的な拒否反応みたいなものが起きてしまうんです(笑)。

まあこれは素人:プロという括りでなく単に性格の問題だったりするのかもしれませんが、とにかく自分がやりたいことはなんとかSQL一発で新市町村のデータ10件分(もちろん関連してる旧市町村も)取ってこれないか?ということなわけです。

つまりもっと具体的に言えば、ページあたり10件のデータが欲しい場合だったらLIMIT(0, 10)のように書きたいわけです。2ページ目以降もLIMIT(10, 10), LIMIT(20, 10)…という具合に10件区切りならoffset値も整然と10件区切り!みたいにしたいわけです。

まあ見た目の問題もありますが実利(実務?)的にも外部ライブラリなどでページングさせる場合などでは、それが出力する値(offset)はだいたい上記のような風になっているわけですから(汎用的なんだから当たり前ですね)それをそのまま使いたいというか…。

で、結局散々試行錯誤した上で変なSQLを書いてみました。
SELECT nid, pref, nc, yomi, gdate, id oid, city oc
FROM
(SELECT DISTINCT
n.id nid,
p.name pref,
n.city nc,
yomi,
gdate
FROM
newcity n,
oldcity o,
joined j,
pref p
WHERE
n.id = j.newcity_id AND
o.id = j.oldcity_id AND
n.pref_id = p.id AND
nc like '%市%'
LIMIT 0, 10
) AS vn,
oldcity o,
joined j
WHERE vn.nid = j.newcity_id
AND o.id = j.oldcity_id

なんか長い…

こんなんで本当に動くのか?という疑いが?(ってかBlog用にちょっと端折ったり付け足したりしてるしな…)それはともかく一応動くはずです(--;

相変わらずJOINをWHERE句に書いてあるところはとりあえず置いといて、思考の流れを順に追って書くと…。

まずヒントとなったのは前回の某氏の教えによるSQL多発方式です。最初に新市町村のデータを件数分取ってきて〜というところがミソで、SQL多発方式では1回目のSELECTでそれを行い、次からはその結果を元に旧市町村のデータを取得するためのSELECTを投げまくるわけですが、上のSQLではその「最初に」の部分をFROM句のサブクエリで行っているわけです。

但し新市町村テーブルから“のみ”件数分取ってきても意味がないためサブクエリ中でも関連テーブルをJOINしています。新市町村名で検索された場合はJOINする必要はないかと思いますが検索条件が旧市町村だった場合当然データが取ってこれないのでJOINしなければなりません。

で、この先が問題なのですがJOINしてしまうことで件数がおかしなこと(ってゆーか旧市町村の件数?)になってしまうわけです。この状態でLIMITをかけても正確なデータは取ってこれないことは前回述べたとおりです。

そこでどうしたかといえば、DISTINCTで新市町村のフィールドをまとめてしまうわけです。要するに検索条件的には旧市町村とJOINする必要がありますが、この時点(サブクエリ)では新市町村の結果しか必要ないわけです。

そしてLIMITを使って件数を絞り込みます。ここまででいったいどのようなデータが取れているかというと、検索条件(新市町村名でも旧市町村名でも:上の例では新市町村名に「市」を含む[nc like '%市%']になっていますが、これは別に旧市町村名が「川」で始まる[oc like '川%']とかなんでも構わないわけです)に一致するoffset値から始まりlimit件数分の新市町村(テーブルと同じフィールドを持つ)のデータになります。

そしてFROM句のサブクエリで新たに作り出された?新市町村テーブル(SQL上では別名をつける必要がありますので、ここではvnとしています)と旧市町村テーブルをあらためてJOINして出力しています。つまりこの部分がSQLを多発しているのと同じことになるのかなぁ???

と、なにやら文章で書くと(文章で書いても?)わけがわからないのはあまり変わらないような気もしますが、とりあえずSQL一発で取れるようになったわけです。もちろんこのSQLが果たして標準的?なものなのか?とか効率がいいのか?とかそういうことは一切考えてません(この時点では)。一発で取れりゃぁいいんです、素人は。

というわけで我流の極みのようなSQLですが、採用決定です!もちろんこの後LIMITの値をいろいろいじって一人悦に入っていたのは言うまでもありません。
ラベル:SQL
posted by ciallost at 13:38| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2007年11月06日

全件取得しないでページングする方法

前回、LIMITを単に適用しただけでは欲しているデータが取れず、途方に暮れている時に、ある方から技?を教えてもらった…というところまで書きましたが、今回はその技をまとめてみたいとおもいます。

方法1:カウントする

質問に対して即座に返ってきた方法としては、スクリプトでひたすらカウントするというものです。つまり新市町村の数が欲しいのに旧市町村の数がわからないためにごちゃごちゃになっているわけで、DBからフェッチする際に両方の数をカウントしていき適宜記憶しておくという方法です。

前述の例でもう少し具体的に書くと、新市町村のデータが10件欲しい場合1ページ目では旧市町村は28件あります。フェッチする際にどちらもカウントしていき新市町村の数が10を超えるところで止めて、その時の旧市町村の数(= 28)を記憶しておきます。次に2ページ目のデータが欲しい時には記憶しておいた28の次からデータを取得するようにして、つまりLIMITのoffset値を28からにしてデータを取得する、というものです。

まあやりたいことを素直に実装する?というのか力技?というのか、質問しておいて某氏には申し訳ありませんが、自分はあまり気に入りませんでした(^^;

問題点はまず旧市町村の数を記憶すると書きましたが、いったいどこに記憶するんだ?というところで、FORMのhiddenフィールドなりURL埋め込みなりとにかくどこかに保持しておいて次のリクエスト時に送ってもらわないとなりません。

それにこの方法だとLIMITのoffset値はわかりますが実はlimit値の方をどのくらいに設定しておけばいいのかが定かではありません。LIMIT 28, 10 では明らかに足りないことは明白ですし、かといってLIMIT 28, 30 ぐらいに適当に増やしておいて〜みたいな方法もなんとなく最初に大きめに取得しておいて後からその中の必要な部分を取ってくる的感が強くてどうもしっくりきません。

で、結局この方法は却下されたわけですが、某氏はこの方法を一通り説明し終った後にポロっと「本当は最初に新市町村の方のデータだけを取ってきて、次に旧市町村のデータを取ってくるようにするのがいいんだよね」みたいなことを言われました。ということで…

方法2:SQLを多発する
まず新市町村のデータをページあたり10件欲しいならそれを取ってきます。次にそのデータを元に関連している旧市町村のデータを順次取ってきます。あとはそれを出力して終了!

これもまあ力技?といえばいえなくもないような…スクリプト側の処理は方法1の「記憶する」みたいな部分もなく、冗長な?データも取得せずに済みますが、その代わり?というかSQLを投げまくらないといけません。

この例の場合だと最初に新市町村のデータを取得するときで1回、ページあたり10件なら旧市町村のデータを取得するのに10回、それと全件数も必要ですから1回、計12回のSELECTを投げることになります。

某氏によればDBが別ストレージ?とかにあるような場合でなければ特に問題ないだろうとのことでしたが、自分的にはやはり投げすぎじゃない?という感がひとしおでした。ページあたり10件なら上述のように12回のSELECTで済みますがこれが20件とか100件とかなっていくとなんだかなぁ…と。

ちなみに後にCakePHPのActiveRecordでHABTMのアソシエーションを張ったデータを取得してみたら12回SELECTが投げられていました(--; 汎用的に作ろうとするとどうしてもそうなるのかな?いずれにしてもSQL多発ってところで自分的には却下されました。

そして解決策のないまま本当に旅に出ることに…
ラベル:PHP
posted by ciallost at 12:02| Comment(1) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2007年11月05日

どうしたら全件取得しないでページングできるのか?

前回、LIMIT句を単純に用いてまんまと失敗した件を書いたわけですが、まああれは実際試したというよりはBlog用という側面も多々あり(^^;ってかちょっと考えればわかりますよね。ということで今回は問題点を考えてみます。

まず何が悪いかってHABTMです。いやHABTMは本当は悪くなくて、いけないのはそれをあのように表示しようとするからおかしなことになっているわけです。

実際に前回のSQLを発行した結果を多次元配列などに格納せずそのままベタで表示すると以下のような感じになっています。

|北海道|函館市|函館市|2004-12-01|
|北海道|函館市|戸井町|2004-12-01|
|北海道|函館市|恵山町|2004-12-01|
|北海道|函館市|南茅部町|2004-12-01|
|北海道|函館市|椴法華村|2004-12-01|
|北海道| 森町 |砂原町|2005-04-01|
|北海道| 森町 | 森町 |2005-04-01|
        :
        :

まあ見ての通りなんですが、自分のような素人はこの結果を見ると単純に「なんてRDBって融通が利かないんだ?!」とか思ってしまうのですが…。数学的な高尚な理論に基づいた方式でやるとどうもこうなってしまうらしいですね(笑)。

で、まあこの状態にLIMITをかけるわけですから前回のような結果になったのもDBの中の人?からすれば至極当然なんでしょうが、素人代表としてはどうにも納得がいかないというか、少なくともダブってるところぐらいなんとかしろよ!とか言いたくなってしまうのです。

|北海道|函館市|函館市|2004-12-01|
|    |    |戸井町|      |
|    |    |恵山町|      |
|    |    |南茅部町|      |
|    |    |椴法華村|      |
|北海道| 森町 |砂原町|2005-04-01|
|    |    | 森町 |      |
        :
        :
(こんな感じに…ってか、かなりズレてるし

上記の表示だと横罫線がないのでこれでいいような風にも見えますが、実際のHTMLのTableタグでは、さらに上下のコラムを連結して、つまり<td rowspan="5">ぐらいまでやってくれないと納得がいきません。

って表示系のことばかり文句たれてますが、まあ素人にとっては最終的な結果の画面が何よりも重要でして、このような表示にするのにおそらく最も最適なデータ形式?が前述の通りの多次元配列だったりするわけです。

ですがあくまで多次元配列に格納する前の状態は↑の最初の方のようになってるわけで、話をLIMITに戻せば当然のように前回の結果となってしまいます。

これは正直いって結構悩みました。スクリプト自体は滅茶苦茶遅い(時がある)とはいえ、LIMITを使わないページングで一応動いているわけですし、手直ししようにもその時点ではどうしたらよいのかすらわからない状態でした。

表示を↑の2番目のようにするにはどうしたらよいのか?とか、HABTMではない単純な?テーブルのページング(LIMITを使う方式も、そうでない方式も)の方法などはネットで調べると結構でてきますが、このような例の場合の手法?というか標準的なやり方みたいなものはなかなか探しても見つかりませんでした。

そもそも質問したいのはヤマヤマでもこれまでココの日記で述べてきたようなことを簡潔に質問するだけの器量もなく(--;途方に暮れる…というとちょっと大袈裟ですが、半ばあきらめてた時もありました。

そんな時以前からネット上で知り合いだったある本職のプログラマの方に質問する機会を得て、ちょっと聞いてみたところ、やはりプロの現場ではこうした状況も日常茶飯事的なものなのか?以外と簡単に答えを教えて頂きました。

最終的にその方法は使わなかったのですが、次回にその辺をまとめてみたいとおもいます。
ラベル:PHP
posted by ciallost at 03:28| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2007年11月04日

LIMITを使ったページング(失敗)

前回は、LIMITを使わずにとりあえず検索結果を全部取得してからスクリプト側でページングする方法を書きましたが、ざっと手順をまとめると…

  1. まず検索結果を全部得る

  2. 件数を数えたいテーブル(この例の場合はnewcity)のユニークな値(ほとんどの場合はPrimaryKeyになってるフィールド、この場合はid)をキーとして多次元配列に格納する

  3. 関連しているテーブルは↑の配列の子配列?として格納する

  4. 件数を数える

  5. めでたくページング完了


というような感じでしょうか。ポイントは多次元配列に格納する部分で、いってみればこれはRDBの正規化?されたデータを元?の親子関係のようなデータ表現?に直す作業という感じなのでしょうか?(詳しいことはよくわかりませんが…)

とにかくこうした形の配列にすることで、ページングできるわけです。ですが検索結果の件数がある程度以上になると全件取ってきてその中から10件とか表示するというやり方では間に合わなくなってきます。そこでSQLのLIMIT句を使おうということになりました。(この時点ではSQLiteを使用していましたが、後にMySQLになりました。LIMITに関しては多少書き方が違うだけでどちらもほぼ同じです。)

LIMIT句とは結果データの一部分を取ってくるためのSQLの句?です。例えば…

SELECT * FROM some_table WHERE some_condition LIMIT(10)
※注:上のLIMITの書き方はSQLiteですね [2007.11.13追記]


などとすればWHERE句までの検索結果が何件だろうと(まあ10件以上を想定していますが…)LIMIT(10)によって10件に絞られます。またoffsetを設定すればデータをどの位置から取ってくるのか?を指定できます。MySQLではSQLiteでは
LIMIT(30, 10); //LIMIT(offset, limit)
LIMIT(10, 30); //LIMIT(limit, offset)
※訂正:MySQLの書式とSQLiteの書式がごちゃ混ぜになってました(--;
括弧を使うのはSQLiteの書き方でしかもパラメータの順番はlimit, offsetのようになります。MySQLでは括弧は書かずにパラメータの順番はoffset, limitになります。というわけで前に書いてあった(打ち消し線)のコードはどちらのDBでも動作しません(TT


などと書けばWHERE句まででヒットした結果の30件目から10件のデータ、という風になります。

簡単!これはイイ! のですが、ページングを実装するためには総件数(LIMITで制限される前の件数)が必要になってくるため、予め

SELECT COUNT(*) FROM some_table WHERE some_condition;


のようにとりあえず全件数だけを取得する必要があります。そのため前述のページング方法よりはSQLを1回多く発行しなければなりません。まあそれでも数百件取ってくるよりは全然効率がいいわけで、実際計ってみても歴然とした差がでてきます。

さらにMySQL限定ですが便利な関数があってこれを使うと上記のCOUNT関数を使うよりも速いというベンチマーク結果もあるようです。とりあえず今回はこの関数については詳しく述べません。

というわけでLIMITの使い方がわかったところで実際に実装してみます。

SELECT pref.name pname, n.id nid, o.id oid, n.city nc, o.city oc, yomi, gdate FROM newcity n, oldcity o, joined j WHERE n.id = j.newcity_id AND o.id = j.oldcity_id AND pref.id = n.pref_id AND (検索条件 ※n.city like '%函館市%' など…) LIMIT 0, 10;


limit_result.png

あれ???なんか変だな?なんか短くね?

というわけで見事に失敗するわけです。しかもCOUNTの値もこれじゃダメダメだし(--;

そして旅に出る〜♪
ラベル:PHP
posted by ciallost at 23:51| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2007年11月03日

LIMITを使わないページング

前回はそもそも何をやりたかったのか?ということでLIMITを使ったページングの実装(前述した通り前者のタイプのページングは実装していましたが…詳細は前回参照)をしたい、というところまで書きましたが、今回はもう少し詳しく前者のLIMITを使わないページングについて説明したいとおもいます。

まずその前にデータベースの構造(テーブルスキーマ?というのか?わからん)とにかくRDBなのでテーブルがどうなっているかを説明しないと解りづらいとおもいますので、以下に…

前述したように平成の大合併で新たに誕生した新市町村と合併前の旧市町村のデータベースです。

TABLE newcity(新市町村)
id: integer primary auto_increment
pref_id: integer
city: varchar
yomi: varchar
gdate: date

TABLE oldcity(旧市町村)
id: integer primary auto_increment
city: varchar

TABLE joined(HABTM関連)
newcity_id: integer
oldcity_id: integer

TABLE pref(都道府県)
id: integer primary
name: varchar


本来はCREATE TABLEのSQLをそのまま書けばよいのでしょうが、そこは素人ってことで(^^;とりあえずこんな感じです。

メイン?のテーブルはnewcity(新市町村テーブル)でこれには新市町村の名前以外にその読みと合併日のフィールドも含まれています。pref_idは都道府県テーブルと単純に関連しているだけです。

oldcity(旧市町村テーブル)とはjoinedテーブルを介して、いわゆるhasAndBelongsToMany(略してHABTM?)と呼ばれる多対多の関連になっています。

と、さも当たり前のようにHABTMとか書いてますが、この構造?に到達するまでにどれだけ回り道をしたことか…、しかも最近知った言葉だし=>HABTM=はぶたむ???

とにかく階層構造?というか親子関係?というのか、なんかそんな感じのデータをRDBで表現するとこうするのがどうやら定石っぽいです。

で、このデータベースから検索結果を取得するわけですが、SQLを書く前に検索結果のイメージを…

search_result.png

見ての通り単純なテーブル(HTMLの)ですが、ポイントは新市町村1行に対して合併前の旧市町村が複数行で表示されてる、ってゆーか文章だと表現しづらいな、要するに<td rowspan="5">みたいな…とにかくそういうことです(--;

んでこれのいったいどこが問題なのか?

上記の画像はページング(DBから全件取得してスクリプトでページングするタイプ)が実装されていて、1ページあたり10件表示したところですが、問題なのは「10件」ってところで、見てのとおり新市町村が10件表示されているのがわかるとおもいます。

が!しかし!?

ここからが今回の核心なのですが、実はRDB上では、というかRDBから取ってきた結果上ではデータ件数は28件です。とおもってよくよく見るとなるほど旧市町村の数は28件あります。ふむふむってことは10件表示したい場合は28件データを取得しとけばいいってことだな…ナワケナイ

当然ですが2ページ目はまた違った件数(旧市町村の)になるわけなんですが、ここからは文章よりも実際のSQLとfetchの結果(の配列)を見てもらったほうが早いでしょう。

というわけでSQL…

SELECT pref.name pname, n.id nid, o.id oid, n.city nc, o.city oc, yomi, gdate FROM newcity n, oldcity o, joined j WHERE n.id = j.newcity_id AND o.id = j.oldcity_id AND pref.id = n.pref_id AND (検索条件 ※n.city like '%函館市%' など…)


JOINを使わないでWHERE句でテーブルを連結していますが、まあとりあえずどっちでもいいや…ってことで。そして配列に格納するところ…

foreach ($result as $row)
{
    $data[$row['nid']]['PREF'] = $row['pname'];
    $data[$row['nid']]['NCITY'] = $row['nc'];
    $data[$row['nid']]['OCITY'][$row['oid']] = $row['oc'];
    $data[$row['nid']]['BIRTH'] = $row['gdate'];
}


多少端折ってます。んでprint_rとか…

Array
(
[1] => Array
(
[PREF] => 北海道
[NCITY] => 函館市
[OCITY] => Array
(
[1] => 函館市
[2] => 戸井町
[3] => 恵山町
[4] => 南茅部町
[5] => 椴法華村
)
[BIRTH] => 2004-12-01
)

[2] => Array
(
[PREF] => 北海道
[NCITY] => 森町
[OCITY] => Array
(
[6] => 砂原町
[7] => 森町
)
[BIRTH] => 2005-04-01
)
:
:


と、こんな形の多次元配列に格納します。newcityのidをキーにすることで後は$dataの数を数えてゴニョゴニョやればページングの完了です。

$hits = count($data);
〜ごにょごにょ


前述した通り検索結果が数10件のうちはこれでもよいのでしょうが、100件あたりから怪しくなってきて、200件ぐらいでヤバイです。このテスト用のDBではそれほどではないかもしれませんが、本物の方はHABTMのリンクテーブルがもう一つあったため、200件の検索結果データから10件表示するだけで10秒以上かかった場合がありました(--;

そんなわけでLIMITの登場です。次回へ続く…
ラベル:PHP
posted by ciallost at 21:21| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2007年11月02日

そもそも何をやりたかったのか?

前回、自作の汚いスクリプトをフレームワークを使うことで書き直せばなんとか先が見えるのではないか?みたいなところまで書きましたが、今回はそもそも何をやりたかったか?というかまずどうしたかったのか?というあたりまで書いてみたいとおもいます。

その前に前回「検索アプリ」としてしか紹介?していませんでしたが、いったいどういったモノを作っていたか?というと…正直言ってこうした半公共の場では絶対書けないのですが(^^;その代わり?というかフレームワークを使うに先立って上記の本物のスクリプトとほぼ同じような機能でデータベースの内容も当り障りのないモノにしたスクリプトを使って説明したいとおもいます。

といっても別にBlogを書くためにわざわざ作ったわけではなく、実際フレームワークを試す際にまずはこのテスト用のスクリプトで作業していますので、メモとして残すにしても問題ないだろうとおもいます。

んでどういうモノか?というと単純な検索アプリです。といっても実物をどこかにアップできればいいのですが、今のところ文章だけで説明しなければならないので果たしてどの程度まで伝わるかどうか…。

データとしては2000年以降、平成の大合併とかいって各地で市町村が合併していますが、そうしてできた新市町村と合併前の旧市町村を使用しています。詳しいテーブルとかはとりあえず後述するとして、そうしたデータベースからいろいろなキーで検索できるような、まあどこにでもあるWEBの検索アプリです。

で、このようなアプリは大抵やることが決まっていて、手続き型方式?でざざっと書くとだいたい以下のとおりだとおもいます。


  1. 検索フォームからのデータを処理する

  2. データベースに問い合わせる

  3. 結果を整形して表示する



まあたったこれだけです。単純なテーブルから検索キーも1、2個程度で表示結果もベタで1頁表示とかなら、それこそ慣れた人なら数分で完成してしまうでしょうし、最近流行りのRoR系のフレームワークでおなじみ?のscaffoldingとかを使えばほぼ自動でできあがってしまうかとおもいます。

最初のうちは自分も割と簡単に作っていけたとおもいます。ところがある時を境にみるみるうちにスクリプトが膨らんでいき、気がついたら前述の通り自分で触るのも恐ろしいモノができあがっていました。

それでもまだバグらしいバグもなく(ただ気づいていないだけの可能性が高いですが)一応動いているからいいようなものの、これ以上、本当にちょっとでも手をつけようものなら、おそらく把握できなくなることは間違いありません。ってゆーか既にわからなくなりかけてる部分もあります。

スクリプトをざっと見た限りでは、そもそも表示系(つまりHTML部分)とロジック部が1枚のファイルになってること自体も問題ですが、FORMから受取ったデータの処理部分とか、データベースクエリの構築部分とか、表示の際の微調整?とか…、とにかく汚い汚い。

んで、ソースが汚いだけならまあ一応動いてるし、ま、いっか!とも言えるのですが、データ件数が増えてくるにつれて(テスト用のデータベースは上記の通り市町村合併のデータなのでこれ以上増えるということはないのですが、本物の方はどんどん増えていくタイプのデータだったので…)動作が許容できないくらいまで遅くなってしまいました。

具体的にどういう問題か?というと、要するにページングのやり方です。慣れた方ならこれでピンとくるとおもいますが、普通検索結果のデータを表示する際に1ページで全件表示するのではなく、結果が多い場合は適当なところで区切って数ページに分けて表示したいわけです。

で、このページングを実装する場合におそらく大きく分けて2通りの方法が考えられるとおもわれ、ひとつはとりあえずデータベースからは結果を全件取得した上でスクリプト上でページングする方法と、もうひとつはデータベースクエリの時点でLIMITなどで予め表示するデータを限定して取ってくる方法です。

やってみればわかることですが前者の方法の方が簡単です。ですが前者の方法が通用するのはせいぜい結果が200件ぐらいまで(まあ環境とかにもよるとおもいますが、自分は200件を過ぎたあたりから「これはちょっとないだろ」とおもい始めました)で、それ以上になるとやはりいくら趣味のスクリプトとはいえ見るに耐えない?状況になります。

で、後者の方法を模索することになるのですが、そもそもこの後者のページングを実装したい、というのが今回のタイトルの何をやりたかったのか?ということなのです。

次回ページングに続く…
ラベル:PHP
posted by ciallost at 19:35| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2007年11月01日

はじめまして〜自己紹介も兼ねて…

え〜はじめまして

このBlogは完全ド素人の私が、従来の手続き型で書かれた(ってか書いた)一応は動くけどももはや自分でも手のつけられなくなったコードを、CodeIgniterで書き直していく過程をメモっていこうと考えています。

最初にお断りしておきますが、自分はプログラミングに関しては全くの素人です。そういう系の学校を出ていないことはもちろん、なんらかのセミナーのような系統立った説明?も未だかつて受けたことがありません。

しかも大昔にCとかC++とかPascalとか(年がばれるか?)に手をつけて、ことごとく挫折してます。唯一モノになった?というか一応その時点で自分が望んでいたものを作ることができたのは、往年のMacについてきたHyperCardとVisuaBasicのMac移植版?のようなRealBasicという、いわゆるどちらもインタプリタ言語と呼ばれるようなものでした。

その後Macの衰退と共にWindowsに移り?RealBasicからも離れ−もちろんWindowsには本家のVisualBasicがありましたが、その頃にはすでにスタンドアローン的なアプリケーションの作成には興味を失っていました−もうプログラミングなんてしないだろうなぁ?と思う時期もありました。

そして時は過ぎ〜HTMLをボチボチ書くようになって、やれCGIだといってPerlで書かれた(人が書いた)スクリプトを設置したり、そうこうするうちにそうしたスクリプトを改造しだし…。気がついた時には自分でPerlのスクリプトを書いていました。まあマトモなものは残りませんでしたが。

そしてPHPが4になった頃からあちこちで爆発的にPHPのスクリプトが増え始め、なんとなくいじってみるとこれがびっくり!Perlと同じことがものすごく簡単にできてしまう!

誤解をまねく恐れがあるのでお断りしておきますが、別にPerlよりPHPの方が言語として優れているとかそういうことではありません、あくまでも自分のような専門家ではない者にとってとっつきやすかった?というかとにかく手軽だったということです。

で、まあ経歴紹介みたいになってしまいましたが、そんな感じでプログラミング?(というのもおこがましい)してまして… ある時どうしてもデータベースを使った検索アプリ?のようなものを作る必要にせまられました。

迫られたといってももちろんプロではないので業務依頼が来たとかそういうことではなく趣味の延長なんですが、それまでPHPで何事かを書いてきた(まあその時点ではRDBも多少はいじっていましたが)といっても、自分にとって本格的?なモノを作るのは初めてに等しかったわけです。

で、どうにかこうにか形になったのがつい今年の8月ぐらいの話で、当然そのスクリプトたるやスパゲティというのもスパゲティに申し訳ないくらいの支離滅裂としたもので、どうにか動いてはいるもののこれ以上なにか新しい機能を付け加えようとすると怖くて何もいじれない!という状況になりました。

そこでなんとかこの状況を改善したいと思っていたところ、フレームワークなる言葉を聞き、なんとなくこれで書き直せばうまくいくんじゃないか?という淡い期待を抱きながらフレームワークをダウンロードしてみました。

次回につづく…
ラベル:PHP
posted by ciallost at 18:35| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする