はじめに
Perl において DB に接続し、SQL を扱うモジュールはもちろん DBI
モジュールな訳だが、これを格段に使いやすくしてくれる DBIx::Simple
モジュールというのがある。
use DBIx::Simple; – 今日のCPANモジュール(跡地)
http://e8y.net/mag/009-dbix-simple/
このサイトのまとめが非常に分かり易い。例を挙げるとこんな感じ。
my $sth = $dbh->prepare(<<SQL); select name, passwd from users where userid = ? SQL $sth->execute($id); my ($name, $passwd) = $sth->fetchrow_array;
users
テーブルからユーザー名とパスワードを取り出している。これを DBIx::Simple
を使って表すと……
my $ds = DBIx::Simple->new($dbh); $ds->query(<<SQL, $id)->into(my ($name, $passwd)); select name, passwd from users where userid = ? SQL
SQL の発行と結果の取得を一発でやってくれる。
DBIx::Simple + SQL::Abstract
更に、これに SQL::Abstract
を連携させることによりモダンな記法が使える。
$ds->select("users", [qw! name passwd !], { userid => $id } )->into(my ($name, $passwd));
この程度だと SQL::Abstract
の恩恵はいまいち感じられないけど、複雑な WHERE
句を使うようになると本領発揮。
SELECT userid, name FROM users WHERE age > 20 AND gender = 'male' AND prefecture IN ('東京都', '静岡県', '岡山県');
これを DBIx::Simple
(SQL::Abstract
)で表すと……
$ds->select("users". [qw! userid, name !], { age => { ">" => 20 }, gender => "male", prefecture => { -in => [qw! 東京都 静岡県 岡山県 !] }, }, );
不等号や IN
句や LIKE
句を使ったときに少し特殊な記法を使うことになるが、慣れれば圧倒的にこちらが見易い。
もっとも、JOIN
や副問い合わせを使い出すと素の SQL より複雑になることもある。そのときはおとなしく最初に挙げた query
メソッドを使おう。
【Perl】DBIx::Simple で副問い合わせや JOIN を使う | blog.delphinus.dev
https://blog.delphinus.dev/2011/09/use-subquery-and-join-with-dbix-simple.html
単行取得メソッド
SQL::Abstract
による簡潔な記法も魅力だが、DBIx::Simple
の真骨頂は様々な結果取得メソッドにある。
list
メソッド
$ds->select("users", [qw! name passwd !], # WHERE 句を省略すると全行取得する ); while (my ($name, $passwd) = $ds->list) { say "ユーザー名: $name, パスワード: $passwd"; }
DBI
でいう fetchrow_array
と同じものだ。
array
メソッド
while (my $r = $ds->array) { say "ユーザー名: $r->[0], パスワード: $r->[1]"; }
同じく、DBI
でいう fetchrow_arrayref
。
hash
メソッド
while (my $h = $ds->hash) { say "ユーザー名: $h->{name}, パスワード: $h->{passwd}"; }
DBI
でいう fetchrow_hashref
。
into
メソッド
while ($ds->into(my ($name, $passwd)) { say "ユーザー名: $name, パスワード: $passwd"; }
DBI
で prepare, bind, execute
をやるのと同じこと。list
メソッドと違って配列のコピーが行われない分速い?(未検証)
全行取得メソッド
単行取得して while
文で回すのではなく、一度でテーブル全体を取得するメソッドたち。
flat
メソッド
my @names = $ds->select("users", [qw! name !], )->flat;
取得カラムが一つだけの時、全行を一つの配列に突っ込んでくれる。
arrays
メソッド
my @data = $ds->select("users", [qw! name age gender !], )->arrays; for (@data) { say "ユーザー名: $_->[0], 年齢: $_->[1], 性別: $_->[2]"; }
一行を配列リファレンスに格納し、更に全行を配列に突っ込んで返す。
hashes
メソッド
my @data = $ds->select("users", [qw! name age gender !], )->hashes; for (@data) { say "ユーザー名: $_->{name}, 年齢: $_->{age}, 性別: $_->{gender}"; }
一行をハッシュリファレンスに格納し、更に全行を配列に突っ込んで返す。
map
メソッド
my %data = $ds->select("users", [qw! userid name !], )->map; while (my ($userid, $name) = each %data) { say "ユーザー ID: $userid, ユーザー名: $name"; }
一つ目のカラムをキーに、二つ目のカラムを値にしたハッシュを返す。カラムが 1 個だけだったり 3 個以上のときには使えない。また、一つ目のカラムの値に重複があった場合もおかしなことになってしまう。
map_arrays
メソッド
my %data = $ds->select("users", [qw! userid name age !], )->map_arrays(0); while (my ($userid, $r) = each %data) { say "ユーザー ID: $userid, ユーザー名: $r->[0], 年齢: $r->[1]"; }
引数に指定したカラム(0
からスタートし、左から順に数える。)をキーに、その他の値を配列リファレンスに格納したものを値にしたハッシュを返す。引数を省略した場合は、一つ目のカラムがキーになる。
map_hashes
メソッド
my %data = $ds->select("users", [qw! userid name age gender !], )->map_hashes("userid"); while (my ($userid, $h) = each %data) { say "ユーザー ID: $userid, ユーザー名: $r->{name}, 年齢: $h->{age}"; }
引数に指定したカラムをキーに、その他の値をハッシュリファレンスに格納したものを値にしたハッシュを返す。この場合は引数を省略できない。
オブジェクトを返すメソッド
DBIx::Simple
のバージョン 1.33
から object
及び、objects
メソッドが実装されている。これは DBIx::Simple
とは別の作者が作った DBix::Simple::OO
と言うモジュールを本家に取り込んだもので、クエリの結果をオブジェクトで返すメソッドだ。
基本編
my $rs = $ds->select("users", [qw! userid name age gender !], ); while (my $obj = $rs->object) { printf "ユーザー ID: %s, ユーザー名: %s, 年齢: %d, 性別: %s", $obj->userid, $obj->name, $obj->age, $obj->gender; }
my @objs = $rs->objects; for my $obj (@objs) { printf "ユーザー ID: %s, ユーザー名: %s, 年齢: %d, 性別: %s", $obj->userid, $obj->name, $obj->age, $obj->gender; }
引数なしでこのメソッドを呼ぶと単純にハッシュのキーがメソッド名になって帰ってくる。これは Object::Accessor
モジュールを使って実装されているので、このモジュールに固有のメソッド名(new
とか mk_accessors
とか)がカラム名にあってはいけない。
応用編
package MyRowObj; sub new { my $class = shift; bless { @_ } => $class; } sub user_info { my $self = shift; "フルネームは $self->{first_name} $self->{last_name} です。"; } 1; package main; # (省略) my $rs = $ds->select("users", [qw! first_name last_name !], ); while (my $obj = $rs->object("MyRowObj")) { say $obj->user_info; # 「フルネームは Bill Gates です。」などといった文字列が表示される }
独自のクラスからそれのインスタンスを返すこともできる。この例では、名前と名字からフルネームを作るメソッドを実装している。こんな単純なものではオブジェクトを作る分遅くなるだけだが、計算が複雑、かつ、よく利用するものを別パッケージにまとめておくといざというとき便利かも知れない。