DBICでselfjoin

こんなテーブルで

多対多

あるタグと一緒に現れるタグの集計をとりたくて、selfjoinをしたかったのでDBIx::Class::ResultSetを参考に書いてみた。

alias指定して、左から右へ配列のリファレンスでつなげていくイメージでよいのかな。join_typeも指定するのを忘れない。

ちょっと探すのに手間取ったのでメモっておく。

DBICを使ってみる

最近はmysqlでデータベースを作ることが多かったんだけど、ちょっとしたもの作るのにmysqlはでかすぎるなあとSQLite+DBICをさわり始めてる。

サンプルはPerl/DBIC - Nekokak's core dumpが、よくまとまっていて非常にわかりやすかったので、これの流れで書いた。が、二箇所ほど悩んだのでメモ。ちなみに、SQLiteのせいかどうかはわかりません。

ちなみに、

  • DBIx::Class 0.07003
  • SQLite version 3.3.5

というバージョン。

last_insert_id

1:多の関係を試す をSQLiteで試す。

まずsqlはこんな感じ

create table user (
    id     integer PRIMARY KEY not null,
    name   varchar(256) not null,
    time   date
);

create table bookmark (
    id      integer NOT NULL PRIMARY KEY,
    user_id integer NOT NULL,
    url     text NOT NULL,
    FOREIGN KEY (user_id) REFERENCES user(id)
);

CREATE INDEX user_id_idx ON bookmark (user_id);

で、データベース作成

sqlite3 kzfm.db < test.sql

Schema,User,Bookmarkモジュールはほとんど同じだがload_componentsにPK::Auto::SQLiteを使う。

use strict;
use warnings;
use base 'DBIx::Class';

__PACKAGE__->load_components(qw/ PK::Auto::SQLite Core/);
__PACKAGE__->table('user');
__PACKAGE__->add_columns(qw/id name/);
__PACKAGE__->set_primary_key('id');
__PACKAGE__->has_many(bookmark => 'Kzfm::Schema::Bookmark', 'user_id');
1;

こんな感じ。

で、実行してみると、、、

my $i = $schema->resultset('User')->create({name => 'kzfm'});
$schema->resultset('Bookmark')->create({
    user_id => $i->last_insert_id ,
    url => 'http://test.com/'}
);

last_insert_idがないと言われた。単にidだけでよいのか

might_have

loginテーブルにマッピングさせるモジュール

のとこでUser,Loginお互いにmight_haveを指定すると->idでIDがゲットできなかった。

use strict;
use warnings;
use base 'DBIx::Class';

__PACKAGE__->load_components(qw/ PK::Auto::SQLite Core/);
__PACKAGE__->table('user');
__PACKAGE__->add_columns(qw/id name/);
__PACKAGE__->set_primary_key('id');
__PACKAGE__->has_many(bookmark => 'Kzfm::Schema::Bookmark', 'user_id');
#__PACKAGE__->might_have(id => 'Kzfm::Schema::Login');
1;

package Kzfm::Schema::Login;

use strict;
use warnings;
use base 'DBIx::Class';
use DateTime::Format::MySQL;

__PACKAGE__->load_components(qw/PK::Auto::SQLite Core/);
__PACKAGE__->table('login');
__PACKAGE__->add_columns(qw/id login_time/);
__PACKAGE__->set_primary_key('id');
__PACKAGE__->might_have(id => 'Kzfm::Schema::User');
__PACKAGE__->inflate_column('login_time', {
    inflate => sub { DateTime::Format::MySQL->parse_datetime(shift); },
    deflate => sub { DateTime::Format::MySQL->format_datetime(shift); },
});

幾つかのパターンを試した結果、参照するほうにmight_haveをつけるらしい。あと、SQLiteの場合日付まわりをどう処理すればいいのかよくわかってない。とりあえずMySQLのフォーマットでも動くような気がするんだけど。

というわけで、時間のあるうち(気力のあるうち?)に、多対多まではいっときたい。