Tag Archives: db

Catalystでnamed_scope風実装

泣きながらPerl/Catalystを書いている今日この頃です。慣れない言語は辛い。

PerlのORマッパーであるDBIx::Classを使ってるんですが、とりあえずDBIx::Class beginnersを見ると、使い方の指針やFat Modelっぽい書き方が載っていて大変良いです。ActiveRecord大好きです。

で、CatalystにはRailsのnamed_scopeのような便利な記法は標準では提供されていません。多分。でもDBIx::Class::ResultSetのサブクラスを使うようにすれば、それっぽいことはできるようになります。

まず

アプリ名をMyAppとします。

my $rs = $c->("DBIC::Foo")が返すのはDBIx::Class::ResultSetのインスタンスで、これはクエリを投げて返ってきた集合に対して処理をするために用います。

一方、例えばmy $foo = $rs->firstが返すのはMyApp::Schema::Result::Fooのインスタンスで、これは個々のレコードに対して処理をするために用います。

なので、RailsのActiveRecordの雰囲気的にはDBIx::Class::ResultSetにモデルのクラスメソッド・named_scopeを実装して、MyApp::Schema::Result::Fooにモデルのインスタンスメソッドを実装する、という感じになります。

::Result::Fooに::ResultSet::Fooを対応付ける

デフォルトだとResultSetにはDBIx::Class::ResultSetが使われるため、全てのテーブルで共通の実装をすることになってしまい、あまり嬉しくありません(共通の処理を実装するなら別ですが)。

なのでまずはMyApp::Schema::Result::FooにMyApp::Schema::ResultSet::Fooを対応付ける必要があります。

この機能は各::Result::*で共通で利用したいので、lib/MyApp/Schema/Result/Base.pm を継承して使えるようにしておきます。

package MyApp::Schema::Result::Base;
use strict;
use warnings;

# 継承されたときに呼ばれる、らしい
sub import {
  my $caller = caller; # 継承の子のパッケージ

  # 継承の子側に対して、resultset_classに::ResultSet::Fooを設定する。
  # クラス名はResultをResultSetに置換したもの。
  $caller->resultset_class(sub {
    my $pkg = $caller;
    $pkg =~ s/Result/ResultSet/;
    return $pkg;
  }->());
}

1;

さて、lib/MyApp/Schema/Result/Foo.pm ではBase.pmを継承するだけです。

package MyApp::Schema::Result::Foo;
use strict;
use warnings;

use MyApp::Schema::Result::Base;

1;

以上で MyApp::Schema::ResultSet::Foo があれば$c->model("DBIC::Foo")はそのインスタンスを返し、無ければこれまでどおりDBIx::Class::ResulSetのインスタンスを返すようになります。

なお、MyApp::Schema::ResultSet::FooはDBIx::Class::ResulSetを継承する必要があります。

package MyApp::Schema::ResultSet::Foo;
use base 'DBIx::Class::ResulSet';
1;

named_scope風のものを::ResultSet::Fooに実装する

package MyApp::Schema::ResultSet::Foo;
use base 'DBIx::Class::ResulSet';

sub recent {
  my ($self, $c) = @_;
  return $self->search({}, { order_by => { -desc => [qw/created_at/] }, rows => 10 });
}

sub public {
  my ($self, $c) = @_;
  return $self->search({ is_public => 1 });
}

1;

searchはまた::ResultSet::*を返すので、チェインすることでさらに絞り込めます。

my $foos = $c->model("DBIC::Foo")->recent->public;

遅延評価されるため、SQLの発行も最後の1回だけです。named_scopeっぽい。


SQL:group byとdistinctの同時使用でcount

MySQL 5.0.x

以下の例でcount(*)count(foo)と同じ。

全データ

fooとbarの2カラムがあって、それらの値はいくつか重複している。

mysql> select * from hoges;
+----+------+------+---------------------+---------------------+
| id | foo  | bar  | created_at          | updated_at          |
+----+------+------+---------------------+---------------------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 | 
|  2 |    2 |   12 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 | 
+----+------+------+---------------------+---------------------+
|  3 |    1 |   11 | 2009-07-27 16:35:33 | 2009-07-27 16:35:33 | 
|  4 |    2 |   12 | 2009-07-27 16:35:33 | 2009-07-27 16:35:33 | 
+----+------+------+---------------------+---------------------+
|  5 |    1 |   21 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 | 
|  6 |    2 |   22 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 | 
+----+------+------+---------------------+---------------------+
|  7 |    3 |   11 | 2009-07-27 16:37:10 | 2009-07-27 16:37:10 | 
|  8 |    4 |   12 | 2009-07-27 16:37:10 | 2009-07-27 16:37:10 | 
+----+------+------+---------------------+---------------------+

countのみ

当然ながら全レコード数8個が得られる。

mysql> select *, count(*) as count from hoges;
+----+------+------+---------------------+---------------------+-------+
| id | foo  | bar  | created_at          | updated_at          | count |
+----+------+------+---------------------+---------------------+-------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     8 | 
+----+------+------+---------------------+---------------------+-------+

distinct付きfooでcount

重複するfooを省いてカウントするから、foo=1-4で4個。

mysql> select *, count(distinct foo) as count from hoges;
+----+------+------+---------------------+---------------------+-------+
| id | foo  | bar  | created_at          | updated_at          | count |
+----+------+------+---------------------+---------------------+-------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     4 | 
+----+------+------+---------------------+---------------------+-------+

barでgroup byしてからdistinctなしでcount

bar=11,12のレコードはそれぞれ3個あるから3とカウントされる。bar=21,22はそれぞれ1個しかないから1とカウントされる。

mysql> select *, count(*) as count from hoges group by bar;
+----+------+------+---------------------+---------------------+-------+
| id | foo  | bar  | created_at          | updated_at          | count |
+----+------+------+---------------------+---------------------+-------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     3 | 
|  2 |    2 |   12 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     3 | 
+----+------+------+---------------------+---------------------+-------+
|  5 |    1 |   21 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
|  6 |    2 |   22 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
+----+------+------+---------------------+---------------------+-------+

barでgroup byしたものをdistinct付きfooでcount

id=1と3、id=2と4はbarだけでなくfooも一致するからそれぞれ1つとカウントされて、id=7,8と合計して2となる。

mysql> select *, count(distinct foo) as count from hoges group by bar;
+----+------+------+---------------------+---------------------+-------+
| id | foo  | bar  | created_at          | updated_at          | count |
+----+------+------+---------------------+---------------------+-------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     2 | 
|  2 |    2 |   12 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     2 | 
+----+------+------+---------------------+---------------------+-------+
|  5 |    1 |   21 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
|  6 |    2 |   22 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
+----+------+------+---------------------+---------------------+-------+

fooとbarの両方でgroup byしてcount

この結果はfooとbarの順番を入れ替えても同じ。

mysql> select *, count(*) as count from hoges group by foo,bar order by id;
+----+------+------+---------------------+---------------------+-------+
| id | foo  | bar  | created_at          | updated_at          | count |
+----+------+------+---------------------+---------------------+-------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     2 | 
|  2 |    2 |   12 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     2 | 
+----+------+------+---------------------+---------------------+-------+
|  5 |    1 |   21 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
|  6 |    2 |   22 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
+----+------+------+---------------------+---------------------+-------+
|  7 |    3 |   11 | 2009-07-27 16:37:10 | 2009-07-27 16:37:10 |     1 | 
|  8 |    4 |   12 | 2009-07-27 16:37:10 | 2009-07-27 16:37:10 |     1 | 
+----+------+------+---------------------+---------------------+-------+