DBIx::Simple + SQL::Abstract
を使うと簡単な SQL だと記述が簡潔にすむのだが、副問い合わせや JOIN
を使い出すと途端に複雑になる。こういう場合は素直に素の SQL を書いた方がいいのだが、無理矢理にでも書くときはどうなるかやってみる。
WHERE
句にリテラル SQL を書く
文字列ではなく、文字列リファレンスを使う。例えば誕生日の「日」より「月」の方が数値として大きい(五月四日とか一〇月三日とか)人のリストを得たいとする。
SELECT id, name FROM directory WHERE MONTH(birthday) > DAY(birthday)
これを DBIx::Simple
で表すとこうなる。
my $ds = DBIx::Simple->new($dbh); my $rs = $sa->select('directory', ['id', 'name'], { 'MONTH(birthday)' => { '>' => \'DAY(birthday)' } }, # { 'MONTH(birthday)' => \'> DAY(birthday)' }, );
リテラル SQL にプレースホルダを使う
次に、年月日が文字列で与えられたとき、誕生日がその日以降である人探す。SQL で書くならこんな感じ1。
SELECT id, name FROM directory WHERE birthday >= STR_TO_DATE('1989/1/8', '%Y/%m/%d')
上記のやり方でそのまま書くとこうなる。
my $rs = $ds->select('directory', ['id', 'name'], { birthday => { '>=' => \"STR_TO_DATE('1989/1/8', '%Y/%m/%d')" }, );
更に、誕生日の部分をプレースホルダにして任意の日付を指定できるようにしよう。
my $ymd = '1989/1/8'; my $rs = $ds->select('directory', ['id', 'name'], { birthday => { '>=' => \["STR_TO_DATE(?, '%Y/%m/%d')" => $ymd] }, );
配列リファレンスのリファレンス(\[ ... ]
) という摩訶不思議なものが出てきた。これを利用すると副問い合わせも可能になる。
副問い合わせを使う
別の住所テーブルを使って、東京都在住の人を探そう。SQL ならこんな感じ。
SELECT id, name FROM directory WHERE id = ( SELECT id FROM address WHERE prefecture = '東京都' )
my $rs = $ds->select('directory', ['id', 'name'], { id => \[<<SQL => '東京都'] }, SELECT id FROM address WHERE prefecture = ? SQL );
これを、任意の数の都道府県に拡大したいときはどうしよう? ここで肝になるのは件の“配列リファレンスのリファレンス”2。この中身は実は SQL::Abstract
の返値に等しい。
my @prefectures = qw! 東京都 神奈川県 千葉県 !; my $sa = SQL::Abstract->new; my ($sub_stmt, @sub_binds) = $sa->select('address', ['id'], { prefecture => { -in => \@prefectures } }, ); my $rs = $ds->select('directory', ['id', 'name'], { id => \[$sub_stmt => @sub_binds] }, );
ここまで来ると記述量も増えてくるし意味あるの?って気にもなる。まあ記述できることに意味があるってことで。
JOIN
を使う
副問い合わせは(記述量は増えるものの)SQL を一切使わない綺麗な構文で書けるのに対して、JOIN
になるとかなり無理矢理な書き方になる。
SELECT d.id, d.name FROM directory d JOIN address a ON d.id = a.id WHERE a.prefecture IN ('東京都', '神奈川県', '千葉県')
これを DBIx::Simple
で書くと……
my @prefectures = qw! 東京都 神奈川県 千葉県 !; my $rs = $ds->select(<<TABLES, directory d JOIN address a ON d.id = a.id TABLES ['d.id', 'd.name'], { 'a.prefecture' => { -in => \@prefectures } }, );
テーブルを指定するところに無理矢理 JOIN
句を押し込むことになる。これなら素直に SQL 文書いた方がいいかもね。
my @prefectures = qw! 東京都 神奈川県 千葉県 !; my $rs = $ds->query(<<SQL, @prefectures); SELECT d.id, d.name FROM directory d JOIN address a ON d.id = a.id WHERE prefecture IN ( @{[ join ', ', ('?') x @prefectures ]} ) SQL