あけました、でDBIC

あけましておめでとうございます。ちょっと遅いかもしれませんが。
今年もバリバリPerlっていきたいです。


というわけでDBIC
Catalystアプリで作ってたんですけどよく使い方がわからないのでDBIC単体でいじくり倒そうかと。


まずはテーブル。

CREATE TABLE genre
( genre_name VARCHAR(8) NOT NULL PRIMARY KEY );
CREATE TABLE place
( place_name VARCHAR(8) NOT NULL PRIMARY KEY );

CREATE TABLE master	(
	id INTEGER(5) NOT NULL PRIMARY KEY AUTO_INCREMENT,
	genre_name VARCHAR(8) NOT NULL,
	place_name VARCHAR(8) NOT NULL,

	FOREIGN KEY (genre_name) REFERENCES genre(genre_name),
	FOREIGN KEY (place_name) REFERENCES place(place_name)
);

INSERT INTO genre VALUES ("genre1");
INSERT INTO genre VALUES ("genre2");

INSERT INTO place VALUES ("place1");
INSERT INTO place VALUES ("place2");

INSERT INTO master VALUES (NULL, "genre1", "place1");

masterテーブルがただgenreとplaceを両方見ているだけのテーブル。


DBICスキーマ作成はここらへんを参考にしてパパッと作っちゃいましょう。で、これを

use MySchema;

BEGIN{ $ENV{DBIC_TRACE} = 1 }

my $s	= MySchema->connect(qw/dbi:mysql:sand foo pass/);
my $m	= $s->resultset("Master");
my $it	= $m->search;

while ( my $i = $it->next )	{
	warn $i->genre_name->genre_name;
	warn $i->place_name->place_name;
}


で、出力を見ると、

SELECT me.id, me.genre_name, me.place_name FROM master me:
SELECT me.genre_name FROM genre me WHERE ( ( ( me.genre_name = ? ) ) ): 'genre1'
genre1 at t.pl line 10.
SELECT me.place_name FROM place me WHERE ( ( ( me.place_name = ? ) ) ): 'place1'
place1 at t.pl line 11.

と、genreとplaceの両方に対してSQLを発行してます。数が少ないといいですけど多くなってきて全件表示とかやるとイヤンな感じ。
こういう単純なときはprefetchを使うとSQLの発行回数を減らせる。というか名前通りですけど。

#prefetchを加える
my $it	= $m->search(undef, { prefetch => "genre_name"  } );
SELECT me.id, me.genre_name, me.place_name, genre_name.genre_name FROM master me  JOIN genre genre_name ON ( genre_name.genre_name = me.genre_name ):
genre1 at t.pl line 10.
SELECT me.place_name FROM place me WHERE ( ( ( me.place_name = ? ) ) ): 'place1'
place1 at t.pl line 11.


文字通り減ってます。perldocみると($rel_name | \@rel_names | \%rel_names)と書いてあるので

my $it	= $m->search(undef, { prefetch => [ qw/genre_name place_name/ ] } );
SELECT me.id, me.genre_name, me.place_name, genre_name.genre_name, place_name.place_name FROM master me  JOIN genre genre_name ON ( genre_name.genre_name = me.genre_
name )  JOIN place place_name ON ( place_name.place_name = me.place_name ):
genre1 at t.pl line 10.
place1 at t.pl line 11.

なんてやると発行回数減ります。詳細表示とか表示するのにいい感じ。


ついでにHashも。テーブルのリレーションを増やす。placeテーブルが参照するplace_refテーブルを作るとか言うお粗末な感じで :-p

CREATE TABLE place_ref
( place_ref_name VARCHAR(8) NOT NULL PRIMARY KEY );
CREATE TABLE place
(
  place_name VARCHAR(8) NOT NULL PRIMARY KEY,
  FOREIGN KEY (place_name) REFERENCES place_ref(place_ref_name)
);

INSERT INTO place_ref VALUES ("place1");
INSERT INTO place_ref VALUES ("place2");
INSERT INTO place VALUES ("place1");
INSERT INTO place VALUES ("place2");
SELECT me.id, me.genre_name, me.place_name, genre_name.genre_name, place_name.place_name FROM master me  JOIN genre genre_name ON ( genre_name.genre_name = me.genre_
name )  JOIN place place_name ON ( place_name.place_name = me.place_name ):
genre1 at t.pl line 10.
SELECT me.place_ref_name FROM place_ref me WHERE ( ( ( me.place_ref_name = ? ) ) ): 'place1'
MySchema::PlaceRef=HASH(0x1f5fa6c) at t.pl line 11.

スキーマ変えたのでDBICスキーマも作り直しましょう。


placeテーブルをplace_refに参照するようになってるのでもちろんHashRefになってます。そこでもう一回メソッド呼び出すとSQLが発行されてしまうのでprefetchを設定。

my $it	= $m->search(undef, { prefetch => [ qw/genre_name /,{place_name=>"place_name"} ] } );

while ( my $i = $it->next )	{
	warn $i->genre_name->genre_name;
	warn $i->place_name->place_name->place_ref_name;
}
SELECT me.id, me.genre_name, me.place_name, place_name.place_name, place_name_2.place_ref_name, genre_name.genre_name FROM master me  JOIN place place_name ON ( plac
e_name.place_name = me.place_name )  JOIN place_ref place_name_2 ON ( place_name_2.place_ref_name = place_name.place_name )  JOIN genre genre_name ON ( genre_name.ge
nre_name = me.genre_name ):
genre1 at t.pl line 10.
place1 at t.pl line 11.

おー適当にやったら出来てしまった。ここでのprefetchのHashRefのkeyはplaceテーブルの項目、valueはplace_refのaliasでいいのかな?
DBICのメソッド全般に言えることっぽいですがScalarRef,ArrayRef,HashRefを入れてやると適正に処理してくれるみたいですね。ここでも通じると。なかなか手ごわそうですDBIC