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... と書きます。