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) | 日記 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック