Class::DBI::Sweetで外部結合を使いたい!

Class::DBI::Sweetで外部結合を使いたい!

目次

 Class::DBI::Sweetを使うといろいろ快適なのですが、残念ながら外部結合が使えません。

 ひょっとするとJOINの構文が各種RDBMSで違うから対応していないのかと思って調べてみた。

RDBMSのJOIN構文

MySQL:http://dev.mysql.com/doc/refman/4.1/ja/join.html

table_reference, table_reference
table_reference [INNER | CROSS] JOIN table_reference [join_condition]
table_reference STRAIGHT_JOIN table_reference
table_reference LEFT [OUTER] JOIN table_reference join_condition
table_reference NATURAL [LEFT [OUTER]] JOIN table_reference
{ ON table_reference LEFT OUTER JOIN table_reference
    ON conditional_expr }
table_reference RIGHT [OUTER] JOIN table_reference join_condition
table_reference NATURAL [RIGHT [OUTER]] JOIN table_reference

PostgreSQL:http://www.postgresql.jp/document/pg813doc/html/queries-table-expressions.html#QUERIES-FROM

T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 ON boolean_expression
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 USING ( join column list )
T1 NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2

Firebird:http://firebird.skr.jp/wiki/SELECT

 =  | table | view | procedure
    [( [,  …])] [alias]

 =   JOIN 
    ON  | ()

 = [INNER] JOIN
    | {LEFT | RIGHT | FULL } [OUTER]} JOIN

SQLite:http://www.net-newbie.com/sqlite/lang.html#select

table-list ::=	table [join-op table join-args]*	
table ::=	table-name [AS alias] |
( select ) [AS alias]	
join-op ::=	, | [NATURAL] [LEFT | RIGHT | FULL] [OUTER | INNER | CROSS] JOIN	
join-args ::=	[ON expr] [USING ( id-list )]

 うーん。どれもみんな普通に使えそうだ*1

 Class::DBI::SweetのJOINを、WHEREに条件を書く形じゃなく、JOIN ONの形に変更できないものか。

 そうすりゃ外部結合なんかにも対応できるようになるんじゃないかなー。has_aのテーブルは現状のままでいいけど、has_manyとかmight_haveのテーブルは外部結合になってたほうがいろいろ便利そうだし。

Class::DBI::Sweet::SQL::Abstractによるマジック

 前にSweetをいじったときになんかJOINを作ってる部分があったのを思い出した。

    my $joins  = delete $self->{cdbi_join_info};
    my $tables = delete $self->{cdbi_table_aliases};

    my $from = $self->{cdbi_class}->table . " ${me}";

    foreach my $join ( keys %{$joins} ) {
        my $table = $tables->{$join}->table;
        $from .= ", ${table} ${join}";
        my ( $l_alias, $l_key, $f_key ) =
          @{ $joins->{$join} }{qw/l_alias l_key f_key/};
        $sql .= " AND ${l_alias}.${l_key} = ${join}.${f_key}";
    }

 ここを変更すればイケそう。

 ただし、無条件に外部結合にすると重くなるので、それを指示できるようにする必要がある。

 Sweet側は$attributeでLEFT JOINするテーブルを指定するのがいいか。Sweet::SQL::Abstract側をどうするか、かな。

Class::DBI::Relationshipを利用する

 searchメソッドの$attributeは使わずに、has_many や might_have で利用している Class::DBI::Relationship の args に設定するといい感じかも。

 こんな感じ:

__PACKAGE__->has_many( cds => 'MyData::CDs', {join_type => 'LEFT'} );
__PACKAGE__->might_have( notes => 'MyData::Notes', {join_type => 'LEFT'} );

 これなら既存のモジュールには影響を与えないし*2、必要なモノだけ外部結合できる。はず。

で、作ったのが Class::DBI::Sweet::More

http://search.cpan.org/~asakura/Class-DBI-Sweet-More-0.01/lib/Class/DBI/Sweet/More.pm

*1:ただし「JOINの場合はINDEXを張っておく必要がある」とか、そういう条件はあるかもしんない。けど、普通はINDEX張るでしょ?

*2:少なくともSweetのテストは問題なく通る。