ExistsReferrer
概要
基本概念
子テーブルの条件で絞り込みをする条件(exists (select ... from ...))を設定します。ExistsReferrer は、絞り込み条件を表す ConditionKey です。
例えば、"モバイルからログインをしたことのある会員(一覧)" というように、 子テーブル(会員ログイン情報)のカラムの条件を利用して、基点テーブル(会員)を絞り込むのに有効です。 また、子テーブル(one-to-many) だけでなく、"とある名前の商品を購入したことのある会員(一覧)" というように、子テーブルの親テーブル(many-to-many)のカラムを利用した絞り込みにも利用できます。 このような条件が exists 句を利用した相関サブクエリで実現されます。
会話上では、いぐじすつりふぁらぁ と表現します。
子テーブル対応の役割を明確に
これは、あくまで子テーブルを使った絞り込みであって、子テーブルのデータ取得(LoadReferrer)ではありません。 DBFluteでは、この違いを混同させずに機能として明確に分けています。
実装方法
実装の流れ ※1.1.x (Java8版)
query() の後、exists[referrer-table]() を呼び出し、SubQuery のコールバック実装を引数に指定します。
e.g. ExistsReferrerの実装手順 (Eclipseでコード補完) {PURCHASE} @Java
cb.q // .q と打って enter
--
cb.query()
--
// .ex で関連テーブル選択、続けて Pu (Purchase) で enter
cb.query().exPu
--
// メソッドが補完されて、引数の "subCBLambda" が選択状態に
cb.query().existsPurchase(subCBLambda);
--
// _ll で補完 (DBFlute補完テンプレートが有効なら)
cb.query().existsPurchase(_ll);
--
// Lambda引数名は purchaseCB にして...
cb.query().existsPurchase(purchaseCB -> {
purchaseCB.query().set... // tabでカーソル移動してcbで検索条件
})
Lambda引数名では、subCBは使わず "テーブルを識別できるCB名" が推奨されます。
SQL上では、FKを構成する関連カラムを使った相関条件が自動的に付与されます。
e.g. 2000円以上の購入をしたことのある会員 @DisplaySql
...
from MEMBER dfloc
where exists (select sub1loc.MEMBER_ID
from PURCHASE sub1loc
where sub1loc.MEMBER_ID = dfloc.MEMBER_ID
and sub1loc.PURCHASE_PRICE >= 2000
)
実装の流れ ※1.0.x (Java6版)
1.0.x (Java6版) であれば...
e.g. ExistsReferrerの実装手順 (Eclipseでコード補完) {PURCHASE} @Java
MemberCB cb = new MemberCB();
cb.q // .q と打って enter
--
cb.query()
--
// 1. .ex まで打つと関連テーブル選択
// 2. PL (PurchaseList) で enter
cb.query().exPL
--
// メソッドが補完されて、引数の "subQuery" が選択状態に
cb.query().existsPurchaseList(subQuery)
--
// "new " (new + 空白一つ) と打って ctrl + space そして enter
cb.query().existsPurchaseList(new )
--
// 実装メソッドの空実装が自動生成される (Eclipse-3.5 以上)
cb.query().existsPurchaseList(new SubQuery<PurchaseCB>() {
public void query(PurchaseCB subCB) {
// TODO Auto-generated method stub
}
})
--
// ctrl (or command) + D で不要な空行やTODOコメントを消して
// サブクエリ(子テーブル)の絞り込み条件を指定
cb.query().existsPurchaseList(new SubQuery<PurchaseCB>() {
public void query(PurchaseCB subCB) {
// 2000円以上の購入をしたことのある会員
subCB.query().setPurchasePrice_greaterEqual(2000);
}
}); // セミコロンを忘れずに
子テーブルの親テーブル (many-to-many)
子テーブルの親テーブル(many-to-many)のカラムも条件として利用できます。ExistsReferrer の中で Query(Relation) を使って実現します。
e.g. 商品名が "S" で始まる商品を購入したことのある会員 @Java
// cb: MemberCB
cb.query().existsPurchase(purchaseCB -> {
purchaseCB.query().queryProduct()
.setProductName_LikeSearch("S", op -> op.likePrefix());
});
子テーブルの子テーブル (one-to-many-to-many)
子テーブルの子テーブル(one-to-many-to-many)のカラムも条件として利用できます。ExistsReferrer の中でさらに ExistsReferrer を使って実現します。
e.g. 2000円以上の購入をしたことのある会員の会員ステータス @Java
// cb: MemberStatusCB
cb.query().existsMember(memberCB -> {
memberCB.query().existsPurchase(purchaseCB -> {
purchaseCB.query().setPurchasePrice_greaterEqual(2000);
});
});
親テーブルの子テーブル (many-to-one-to-many)
親テーブルの子テーブル(many-to-one-to-many)のカラムも条件として利用できます。Query(Relation) の後に ExistsReferrer を使って実現します。
e.g. 2000円以上の購入をしたことのある会員の会員ログイン @Java
// cb: MemberLoginCB
cb.query().queryMember().existsPurchase(purchaseCB -> {
purchaseCB.query().setPurchasePrice_greaterEqual(2000);
});
サブクエリが空条件でも意味あり
サブクエリに何も条件がなくても、ExistsReferrer は有効です。会員と購入の関係で言えば、"(一度でも何かしら)購入したことのある会員(一覧)" という業務的な条件を意味します。
e.g. 空条件のExistsReferrer {MEMBER, PURCHASE} @Java
// cb: MemberCB
cb.query().existsPurchase(purchaseCB -> {
// 空条件:(一度でも何かしら)購入したことのある会員(一覧)
});
e.g. 空条件のExistsReferrer {MEMBER, PURCHASE} @DisplaySql
...
from MEMBER dfloc
where exists (select sub1loc.MEMBER_ID
from PURCHASE sub1loc
where sub1loc.MEMBER_ID = dfloc.MEMBER_ID
)
もし、サブクエリ内の条件値が存在しないときに ExistsReferrer 自体を無効にしたいというのであれば、if 文で分岐させます。
e.g. 空条件のExistsReferrer {MEMBER, PURCHASE} @Java
// cb: MemberCB
Integer price = ...;
if (price != null) {
cb.query().existsPurchase(purchaseCB -> {
purchaseCB.query().setPurchasePrice_greaterEqual(price);
});
}
existsではなくinScopeSubQueryで
SQLとして、exists (select ...) ではなく in (select ...) で実行したい場合、ExistsReferrer のオプションで実現できます。これを InScopeSubQuery を呼びます。
exists で利用する ConditionBean にて、useInScopeSubQuery() を呼ぶと、そのサブクエリはSQL上において in (select ...) 方式になります。
e.g. InScopeSubQueryを指定したExistsReferrer {MEMBER, PURCHASE} @Java
cb.query().existsPurchase(purchaseCB -> {
purchaseCB.useInScopeSubQuery();
purchaseCB.query().setPurchasePrice_greaterEqual(2000);
});
e.g. InScopeSubQueryになったSQL @DisplaySql
...
from MEMBER dfloc
where dfloc.MEMBER_ID in (select sub1loc.MEMBER_ID
from PURCHASE sub1loc
where sub1loc.PURCHASE_PRICE >= 2000
)
リレーションシップの関係性によって、InScopeSubQueryの方がパフォーマンスが良いと想定される場面で利用します。 基点テーブルが他の絞り込み条件でどれだけ絞り込まれるか?サブクエリの中の条件でどれだけ子テーブルが絞り込まれるか? この辺がポイントになると想定されます。
言葉のニュアンスとしては、ExistsReferrerという "子テーブルが存在するかどうか" という絞り込みに対して、実現方法が "exists" (デフォルト) と "in (select ...)" の二つがあるという風に言えます。
メソッド仕様
基本仕様
- 引数の指定
- 引数の SubQuery は必須です。
- 同テーブルに対する複数条件の指定
- 同じ関連テーブルに対しての ExistsReferrer 複数回呼び出しは、呼び出した分だけ条件になります。
- サブクエリのConditionBean
- サブクエリの ConditionBean は、絞り込み条件だけの指定に利用するものです。 SetupSelect や OrderBy などサブクエリとして必要のない機能は呼び出してはいけません。
- OnClause や InlineView では不可
- OnClause や InlineView の中の条件では利用できません。
サポートされる関連テーブル
one-to-many に加えて、one-to-one (業務的one-to-oneを除く)の関連に対してサポートされます。
但し、one-to-one は、DBFluteでは ForeignTable として扱われ、Query(Relation) で簡単に条件を設定できるので、ExistsReferrer を使う必要性はあまりないかもしれません。 (本来、実装上で Referrer と言った場合、基本的に one-to-one は含まれません。昔々に作って消すタイミングを失ってしまったとも言えます)
否定条件:NotExistsReferrer
否定条件の対象値の列挙として、NotExistsReferrer もあります。条件が否定になっただけで仕様は ExistsReferrer と全く同じです。cb.query().exists... ではなく cb.query().notExists... と書きます。