2.1. テーブルのリレーション¶
web2py のモデルでは、テーブルのリレーションの定義ができます。定義や利用方法について、リレーションのパターン毎に紹介していきます。
2.1.1. 一対多のリレーション¶
一対多のリレーションでは、参照側テーブルに参照用フィールドを設定します。設定をサンプルで示してみます。
db.define_table('person',
Field('name'), format='%(name)s')
db.define_table('dog',
Field('owner_id', db.person), # フィールド設定でTableインスタンスを指定
Field('name'))
リレーション設定は Field クラスのコンストラクタにて、 referenceキーワード でフィールドタイプを指定します。referenceキーワードの代わりに Tableインスタンスでも指定可能です。
リレーションを設定したテーブルから、参照先テーブルのフィールド値を取得するには 再帰的select もしくは テーブル結合(join) を利用します。
また参照先のテーブルに formatオプションが設定されている場合は、represent属性などでアクセス可能です。formatオプションは formatオプションの動作 を 参照ください。
2.1.2. 多対多のリレーション¶
DALで多対多のリレーションを実現する方法としては2つあります。一つはリレーション用テーブルを用意する方法です。 もう一つはlistフィールドを用いる方法です。
リレーション用テーブルを使用した多対多¶
リレーション用テーブルを使用する多対多は、RDBの正規化理論に従った手法です。パフォーマンスや保守性に問題ない限り、正規化理論に 従ってテーブルが切り分けられていきます。サンプルの定義を示します。
db.define_table('person',
Field('name'))
db.define_table('cat',
Field('name'))
db.define_table('ownership',
Field('owner_id',db.person),
Field('cat',db.cat))
ownershipテーブルがリレーションを管理します。テーブルの参照では一般的に テーブル結合(join) を使用します。
>>> rows = db((db.cat.id==db.ownership.cat) & (db.person.id==db.ownership.owner_id)).select()
>>> for row in rows:
... print row.cat.name, row.person.name
Motacesa Pacedada
Pasamamo Socepopa
Taducoto Cosotama
次のように記述することも可能です。 Set インスタンスに追加した条件は AND(&) で設定されます。
>>> set = db((db.cat.id==db.ownership.cat) & (db.person.id==db.ownership.owner_id)) # Setインスタンス定義
>>> for row in set(db.person.name=='Socepopa').select(): # setに条件追加すると共にselectメソッド呼び出し
... print row.cat.name, row.person.name
Pasamamo Socepopa
注意点としては、GAEではjoinをサポートしていません。このためjoinを使用せず検索するか、リストフィールドを使った多対多モデル を利用します。
参考: Many to Many | 多対多
リストフィールドを使用した多対多¶
リストフィールドは、リスト型のデータを持つフィールドのことです。これは NoSQL であるGAEの Datastore が持つリスト型 フィールドと同じ機能です。web2pyはこのリストフィールドを Datastore のみならず、他のRDBに対しても実現しています。 リストフィールドについては リストフィールド も参照ください。
リストフィールドを使ったリレーションではフィールドタイプとして、 list:reference
を使用します。また参照先のテーブルに
format オプションを設定しておくと、比較的容易にアクセスが可能になります。これらの設定を行ったサンプルは次のようになります。
db.define_table('person',
Field('name'),format='%(name)s')
db.define_table('cat',
Field('name'),
Field('owner_id','list:reference person'))
list:reference
フィールドを使っったテーブルの読み出しは次のようになります。
>>> for row in db(db.cat).select():
... print row.name, db.cat.owner_id.represent(row.owner_id)
Datoposa Podatoso, Ducepapa
Sataduda Podatoso, Pamadapa, Dudaduto
Socedata Sapopadu, Dudaduto
SQLFORM や SQLTABLE を利用した場合は、設定した formatオプションは自動で表示します。詳細は formatオプションの動作 を参照ください。
もしformatオプションを使わない場合、例えば次のようなコードになります。
>>> for row in db(db.cat).select():
... for owner_id in row.owner_id:
... print row.name, db.person(owner_id).name
Datoposa Ducepapa
Datoposa Podatoso
Sataduda Dudaduto
リストフィールドに対し検索するには、 contains()
メソッドを使用します。
>>> for row in db(db.cat.owner_id.contains('2')).select(): # owner_idが2のcatデータを取得
... print row.name, db.cat.owner_id.represent(row.owner_id)
Datoposa Podatoso, Ducepapa
Sataduda Podatoso, Pamadapa, Dudaduto
containsメソッドはリストフィールドでは、個々の値の検索を行います。