DBIx::Skinnyを使った際のPaging方法考察
DBIx::Skinnyにはネイティブにpagingをしてくれる便利機能はありません。
(最近ないないばっかり言ってるな)
DBICとかだと$rs->pagerみたいにしてData::Pageのオブジェクトを返してくれるんですが、
Data::Pageのオブジェクトを作る際に、内部でcountを発行しています。
pagingするにはSQLにLIMIT/OFFSETをかけてると、思うのでLIMIT/OFFSETを掛けなかった際の
トータルな件数を取るためですね。
結構このcountが馬鹿にならないくらい内部で発行されることがあるのでSkinnyではあえてサポートしなかったです。
あと、独自にSQLを書かせる事をお題目にあげているので、
独自に書かれたSQLを内部でごちゃごちゃしてcount発行するとかヤッテラレナイてのもあります。
ただ、アプリを作ってる時にpagingは必須なのでどうすれば良いのかのサンプルをば。
一個目がMySQL限定のやり方です。
サンプルは
http://github.com/nekokak/p5-dbix-skinny-sample/tree/master/paging-mysql/
にあります。
MySQLの機能にSQL_CALC_FOUND_ROWS/FOUND_ROWS()というのがあるのでそれを使います。
参考:http://dev.mysql.com/doc/refman/4.1/ja/miscellaneous-functions.html
これを使えば、LIMIT/OFFSETを掛けない際に実際どれだけのレコード数を取るのかをGETできるので
こんな感じでやってやります。
package Demo::DBPager; use strict; use warnings; use Data::Page; sub users { my ($self, $db, $page) = @_; my $limit = 2; my $offset = $limit*($page-1); my $itr = $db->search_by_sql(q{SELECT SQL_CALC_FOUND_ROWS * FROM user LIMIT ? OFFSET ?},[$limit, $offset]); my $rows = $db->search_by_sql('SELECT FOUND_ROWS() AS row')->first; my $pager = Data::Page->new( $rows->row, $limit, $page ); return ($itr, $pager); } 1;
呼び出し側はこんな感じ
my ($itr, $pager) = Demo::DBPager->users($db, 1); warn Dumper $pager; warn Dumper (map {$_->get_columns} $itr->all); ($itr, $pager) = Demo::DBPager->users($db, 2); warn Dumper $pager; warn Dumper (map {$_->get_columns} $itr->all);
内部で件数をカウントする分DBの負荷にはなるかと思いますが、自前でcountを取るよりは早いし負荷も抑えられるはずです。
これがMySQLの機能でやる方法。
次にRDBMSに依存しない形の方法を
サンプルは
http://github.com/nekokak/p5-dbix-skinny-sample/tree/master/paging/
ここにあります。
全部の件数を取得せずにLIMIT/OFFSETを掛けた時に次のレコードがあるかを調べることによって、
次のページがあるかを判定する方法になります。
ちょっとごちゃごちゃしてますが、
2件表示する場合、3件のデータを取れるかをためして、
3件あれば次のページがある、2件以下の場合は次のページはないよという方法。
package Demo::DBPager; use strict; use warnings; use Data::Page; sub users { my ($self, $db, $page) = @_; my $limit = 2; my $offset = $limit*($page-1); my @rows = map { $_->get_columns } $db->search_by_sql(q{SELECT * FROM user LIMIT ? OFFSET ?},[($limit+1), $offset]); my $has_next = scalar(@rows) > $limit ? 1 : 0; if ($has_next) { pop @rows; } my $total_rows = ($limit * $page) + $has_next - (scalar(@rows) < $limit ? 1 : 0); my $pager = Data::Page->new( $total_rows, $limit, $page, ); return (\@rows, $pager); } 1;
使い方は特に変わらず
my ($rows, $pager) = Demo::DBPager->users($db, 1); warn Dumper $pager; warn Dumper $rows; ($rows, $pager) = Demo::DBPager->users($db, 2); warn Dumper $pager; warn Dumper $rows; ($rows, $pager) = Demo::DBPager->users($db, 3); warn Dumper $pager; warn Dumper $rows;
この方法の場合はRDBMSに依存せずに書くことができますが、
totalの件数が分からないので表示のさせ方がしょぼくなりますね。
まぁ、こんな感じでやればSkinnyでもページングさせることは可能というお話でした。