2.2. テーブル結合(join)

web2py では明示的なテーブル結合の設定として次のものがあります。

  • Setクラスのコンストラクタでの内部結合(inner join)
  • Setクラスのselectメソッドでの内部結合(inner join)
  • Setクラスのselectメソッドでの左外部結合(left outer join)

これらについて順番に説明していきます。

2.2.1. テーブル結合(join)の基本

1. 内部結合(inner join)

内部結合は、2つのテーブルの一致する列の行だけを組み合わせて一つの行にします。一致しない行は除外されます。

../../_images/web2py_dal_017r.JPG

参考: Inner Joins内部結合(Inner Joins)

1.1. Setインスタンス生成時での内部結合定義

Setインスタンス生成時( インスタンス化 )に、パラメータのQueryインスタンスを使用して内部結合の設定ができます。

>>> print db(db.person.id==db.dog.owner_id).select()
person.id,person.name,dog.id,dog.owner_id,dog.name
2,Socepopa,1,2,Comoceta
3,Cosotama,2,3,Soducomo
3,Cosotama,3,3,Cosasamo

パラメータとして渡す Query インスタンスに、内部結合での条件式を記述します。

サンプルでのSQL文は次のようになります。

>>> print db(db.person.id==db.dog.owner_id)._select()
SELECT  person.id, person.name, dog.id, dog.owner_id, dog.name FROM person, dog
WHERE (person.id = dog.owner_id);

1.2. selectメソッドでの内部結合定義

Setクラスの select() メソッドでも内部結合の設定が可能です。

>>> print db().select(db.person.ALL, db.dog.ALL, join=db.dog.on(db.person.id==db.dog.owner_id))
person.id,person.name,dog.id,dog.owner_id,dog.name
2,Socepopa,1,2,Comoceta
3,Cosotama,2,3,Soducomo
3,Cosotama,3,3,Cosasamo

selectメソッドの joinオプションを使います。また併せて on() メソッドも使用します。

サンプルでのSQL文は次のようになります。

>>> print db()._select(db.person.ALL, db.dog.ALL, join=db.dog.on(db.person.id==db.dog.owner_id))
SELECT  person.id, person.name, dog.id, dog.owner_id, dog.name FROM person JOIN dog
ON (person.id = dog.owner_id);

SetコンストラクタはWHERE文で内部結合の定義しています。しかしこちらではJOIN句を使っています。

2. 左外部結合(left outer join)

左外部結合では、左側テーブルは(一致しなくても)すべての行を組み合わせます。一致しないテーブルのデータはNULL値になります。

../../_images/web2py_dal_018r.JPG

この結合の設定では、 select() メソッドの left オプションを使用します。

>>> print db().select(db.person.ALL, db.dog.ALL,left=db.dog.on(db.person.id==db.dog.owner_id))
person.id,person.name,dog.id,dog.owner_id,dog.name
1,Pacedada,<NULL>,<NULL>,<NULL>
2,Socepopa,1,2,Comoceta
3,Cosotama,2,3,Soducomo
3,Cosotama,3,3,Cosasamo

ここで左側テーブルというのは、left オプションで指定しない方のテーブル(結合したテーブルではない方)を指しています。

サンプルでのSQL文は次のようになります。

>>> print db()._select(db.person.ALL, db.dog.ALL,left=db.dog.on(db.person.id==db.dog.owner_id))
SELECT  person.id, person.name, dog.id, dog.owner_id, dog.name FROM person LEFT JOIN dog
ON (person.id = dog.owner_id);

参考: Left Outer Join左外部結合(Left Outer Join)

2.2.2. 複雑なテーブル結合(join)1

少し複雑なテーブル結合について、考えてみたいと思います。3つのテーブルを使ったテーブル結合です。

1. 内部結合(inner join)

次のように複数の内部結合がある場合、3つのテーブルの一致する列の行だけを組み合わせて一つの行にします。一致しない行はすべて除外されます。

../../_images/web2py_dal_029r.PNG

1.2. Setインスタンス生成時での内部結合定義

Queryインスタンスを使用して複数の内部結合を記述するには、 論理演算子 を使用します。

>>> print db((db.person.id==db.dog.owner_id)&(db.dog.hospital==db.animal_hospital.id)).select()
person.id,person.name,dog.id,dog.owner_id,dog.hospital,dog.name,animal_hospital.id,animal_hospital.name
2,Socepopa,1,2,1,Comoceta,1,North
2,Socepopa,2,2,1,Soducomo,1,North

論理演算子を使わず、 関数へのエミュレーション で記述することも可能です。

>>> print db(db.person.id==db.dog.owner_id)(db.dog.hospital==db.animal_hospital.id).select()
person.id,person.name,dog.id,dog.owner_id,dog.hospital,dog.name,animal_hospital.id,animal_hospital.name
2,Socepopa,1,2,1,Comoceta,1,North
2,Socepopa,2,2,1,Soducomo,1,North

1.2. selectメソッドでの内部結合定義

select() メソッドで複数の内部結合を設定するには、joinに複数の条件を&演算子で結合して指定します。

>>> print db().select(db.person.ALL, db.dog.ALL, db.animal_hospital.ALL,
... join=db.dog.on((db.person.id==db.dog.owner_id)&(db.animal_hospital.id==db.dog.hospital)))
person.id,person.name,dog.id,dog.owner_id,dog.hospital,dog.name,animal_hospital.id,animal_hospital.name
2,Socepopa,1,2,1,Comoceta,1,North
2,Socepopa,2,2,1,Soducomo,1,North

この他 join パラメータにリストもしくはタプルで複数指定することで、実行することも可能です。

>>> print db().select(db.person.ALL, db.dog.ALL, db.animal_hospital.ALL,
... join=[db.person.on(db.person.id==db.dog.owner_id),
... db.animal_hospital.on(db.animal_hospital.id==db.dog.hospital)])
person.id,person.name,dog.id,dog.owner_id,dog.hospital,dog.name,animal_hospital.id,animal_hospital.name
2,Socepopa,1,2,1,Comoceta,1,North
2,Socepopa,2,2,1,Soducomo,1,North

2. 左外部結合(left outer join)

左外部結合で複数の結合が存在した場合でも設定可能です。左側テーブル(db.person)はすべての行を組み合わせます。一致しないテーブルのデータはNULL値になります。

../../_images/web2py_dal_030r.PNG

left パラメータに、リストもしくはタプルで複数指定します。

>>> print db().select(db.person.ALL, db.dog.ALL, db.animal_hospital.ALL,
... left=[db.dog.on(db.person.id==db.dog.owner_id),
... db.animal_hospital.on(db.animal_hospital.id==db.dog.hospital)])
person.id,person.name,dog.id,dog.owner_id,dog.hospital,dog.name,animal_hospital.id,animal_hospital.name
1,Pacedada,<NULL>,<NULL>,<NULL>,<NULL>,<NULL>,<NULL>
2,Socepopa,1,2,1,Comoceta,1,North
2,Socepopa,2,2,1,Soducomo,1,North
3,Cosotama,3,3,<NULL>,Cosasamo,<NULL>,<NULL>

2.2.3. 複雑なテーブル結合(join)2

さらに複雑なテーブル結合を考えてみます。一方のテーブルから同じテーブルに複数の結合を行なってみます。

1. 内部結合(inner join)

次のように同じテーブルに対して複数の内部結合がある場合、一致する列の行だけを組み合わせて一つの行にします。一致しない行はすべて除外されます。

../../_images/web2py_dal_031r.PNG

このようなタイプの結合では、selectメソッドでの内部結合定義を利用します。また同じテーブル同士で複数の結合があるので、別名を付ける必要があります。

>>> print db().select(db.cat.ALL, db.owner1.ALL, db.owner2.ALL,
... join=[db.person.with_alias('owner1').on(db.owner1.id==db.cat.owner1),
... db.person.with_alias('owner2').on(db.owner2.id==db.cat.owner2)])
cat.id,cat.name,cat.owner1,cat.owner2,owner1.id,owner1.name,owner2.id,owner2.name
1,Comoceta,2,3,2,Socepopa,3,Cosotama
2,Suducomo,2,1,2,Socepopa,1,Pacedada

つまりcatテーブルのowner1フィールドの結合に対しては with_alias() メソッドで owner1 という別名を、 owner2フィールドに対しては別名として owner2 を設定しています。

サンプルでのSQL文は次のようになります。

>>> print db()._select(db.cat.ALL, db.owner1.ALL, db.owner2.ALL,
... join=[db.person.with_alias('owner1').on(db.owner1.id==db.cat.owner1),
... db.person.with_alias('owner2').on(db.owner2.id==db.cat.owner2)])
SELECT  cat.id, cat.name, cat.owner1, cat.owner2, owner1.id, owner1.name, owner2.id, owner2.name
FROM cat JOIN person AS owner1 ON (owner1.id = cat.owner1) JOIN person AS owner2 ON (owner2.id = cat.owner2);

2. 左外部結合(left outer join)

同じテーブルに対して複数の内部結合がある場合も、左側テーブル(db.cat)はすべての行を組み合わせます。一致しないテーブルのデータはNULL値になります。

../../_images/web2py_dal_032r.PNG

左外部結合でも内部結合と同様に、別名で設定する必要があります。つまり、join パラメータの代わりに left を使用すれば、そのまま実行できます。

>>> print db().select(db.cat.ALL, db.owner1.ALL, db.owner2.ALL,
... left=[db.person.with_alias('owner1').on(db.owner1.id==db.cat.owner1),
... db.person.with_alias('owner2').on(db.owner2.id==db.cat.owner2)])
cat.id,cat.name,cat.owner1,cat.owner2,owner1.id,owner1.name,owner2.id,owner2.name
1,Comoceta,2,3,2,Socepopa,3,Cosotama
2,Suducomo,2,1,2,Socepopa,1,Pacedada
3,Cosasamo,3,<NULL>,3,Cosotama,<NULL>,<NULL>