2.1. テーブルのリレーション

web2py のモデルでは、テーブルのリレーションの定義ができます。定義や利用方法について、リレーションのパターン毎に紹介していきます。

2.1.1. 一対多のリレーション

../../_images/web2py_dal_025r.JPG

一対多のリレーションでは、参照側テーブルに参照用フィールドを設定します。設定をサンプルで示してみます。

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オプションの動作 を 参照ください。

参考: One to Many Relation1対多のリレーション

2.1.2. 多対多のリレーション

../../_images/web2py_dal_026r.JPG

DALで多対多のリレーションを実現する方法としては2つあります。一つはリレーション用テーブルを用意する方法です。 もう一つはlistフィールドを用いる方法です。

リレーション用テーブルを使用した多対多

../../_images/web2py_dal_027r.JPG

リレーション用テーブルを使用する多対多は、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多対多

リストフィールドを使用した多対多

../../_images/web2py_dal_028r.JPG

リストフィールドは、リスト型のデータを持つフィールドのことです。これは 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メソッドはリストフィールドでは、個々の値の検索を行います。

参考: Many to Many多対多、list:<type>、contains