(Myself)ScalarCondition

概要

基本概念

あるカラムと そのカラムの導出値(最大値や合計値) との比較で絞り込みます。

例えば、"一番若い会員" や "購入平均価格を上回る購入をしている会員" というように、 あるカラムとそのカラムの導出値(最大値や合計値)を比較する絞り込み条件を設定することができます。 where 句のサブクエリを使って条件を設定します。 あるカラムと 別のカラムの導出値 を比較することはできません。

会話上では、すからこんでぃしょん と表現します。俗に "最大値レコードの取得" と表現しても ScalarCondition であることがわかります。

ScalarSelect との違い

Behavior の ScalarSelect との違いは、ScalarSelect が導出カラムの値(スカラ値)を取得するのに対し、ScalarCondition は、SQL内でそのスカラ値を使って絞り込みをすることです。

実装方法

実装の流れ ※1.1.x (Java8版)

query() の後、scalar_[condition-key]() を呼び出し、その後、max() や sum() など関数メソッドを選び、SubQuery のコールバック実装を引数に指定します。コールバックの中では、SpecifyColumn を使って比較対象カラムを(一つ)指定します。

e.g. ScalarConditionの実装手順 (Eclipseでコード補完) {MEMBER, BIRTHDATE} @Java
// cb: MemberCB
cb.q // .q と打って enter
--

cb.query()
--

// .sc まで打つと比較条件選択、そして、E (Equal) で enter
//  => Equal, GreaterThan, LessThan,
//     GreaterEqual, LessEqual, NotEqual
cb.query().scE
--

// チェーン呼び出しで、max() や min()、sum() などの関数を選んで enter
//  => max(), min(), sum(), avg()
cb.query().scalar_Equal().max
--

// メソッドが補完されて、引数の "scalarCBLambda" が選択状態に
cb.query().scalar_Equal().max(scalarCBLambda)
--

// cbLambdaの部分で、_ll (補完テンプレートが有効なら)
cb.query().scalar_Equal().max(_ll)
--

// Lambda引数名はテーブルを識別できるCB名(memberCB)にして...
cb.query().scalar_Equal().max(memberCB -> {
    memberCB.specify().columnBirthdate(); // 比較対象カラムの指定
    memberCB.query()... // 絞り込み条件
})
--

// セミコロン ';' を忘れずに
cb.query().scalar_Equal().max(memberCB -> {
    memberCB.specify().columnBirthdate(); // 比較対象カラムの指定
    memberCB.query()... // 絞り込み条件
});

 or

// partitionBy()などを利用する時は、チェーンで続けて実装
cb.query().scalar_Equal().max(memberCB -> {
    memberCB.specify().columnBirthdate(); // 比較対象カラムの指定
    memberCB.query()... // 絞り込み条件
}).partitionBy(...);
e.g. ScalarConditionを使って一番若い会員(のレコード)を取得 @DisplaySql
...
  from MEMBER dfloc
 where dfloc.BIRTHDATE = (select max(sub1loc.BIRTHDATE)
                            from MEMBER sub1loc
       )
...

適切な比較条件、および、関数を選んで実装して下さい。関数によっては利用できるカラムのデータ型が限定されるものもあります。

e.g. 全体平均より多い購入価格の購入を検索 @Java
// cb: PurchaseCB
cb.query().scalar_GreaterThan().avg(purchaseCB -> {
    purchaseCB.specify().columnPurchasePrice();
});

実装の流れ ※1.0.x (Java6版)

query() の後、scalar_[condition-key]() を呼び出し、その後、max() や sum() など関数メソッドを選び、SubQuery のコールバック実装を引数に指定します。コールバックの中では、SpecifyColumn を使って比較対象カラムを(一つ)指定します。

e.g. ScalarConditionの実装手順 (Eclipseでコード補完) {MEMBER, BIRTHDATE} @Java
MemberCB cb = new MemberCB();
cb.q // .q と打って enter
--
cb.query()
--
// 1. .sc まで打つと比較条件選択
// 2. E (Equal) で enter
//  => Equal, GreaterThan, LessThan,
//     GreaterEqual, LessEqual, NotEqual
cb.query().scE
--

cb.query().scalar_Equal()
--

// max() や min()、sum() などの関数メソッドを選んで enter
//  => max(), min(), sum(), avg()
cb.query().scalar_Equal().max
--

// メソッドが補完されて、引数の "subQuery" が選択状態に
cb.query().scalar_Equal().max(subQuery)
--

// "new " (new + 空白一つ) と打って ctrl + space そして enter
cb.query().scalar_Equal().max(new )
--

// 実装メソッドの空実装が自動生成される (Eclipse-3.5 以上)
cb.query().scalar_Equal().max(new SubQuery<MemberCB>() {
    
    public void query(MemberCB subCB) {
        // TODO Auto-generated method stub
        
    }
})
--

// ctrl (or command) + D で不要な空行やTODOコメントを消して
// 比較対象カラムと絞り込み条件を指定
cb.query().scalar_Equal().max(new SubQuery<MemberCB>() {
    public void query(MemberCB subCB) { // 一番若い会員
        subCB.specify().columnBirthdate(); // 比較対象カラムの指定
    }
});

max()でequal()でも一件とは限らない

max() で equal() したからといって、結果が一件になるとは限りません。例えば、"一番若い会員" と言った場合でも、生年月日カラムがユニークでなければ、複数の会員が対象となる可能性があります。 そういった場合は、一件検索ではなくリスト検索になります。業務的な結果件数の可能性を考慮して、Behavior のメソッドを選んで下さい。

e.g. 一番若い会員は何人もいるかもしれない @Java
List<Member> memberList = memberBhv.selectList(cb -> { // 複数件の可能性
    cb.query().scalar_Equal().max(memberCB -> { // 一番若い会員
        memberCB.specify().columnBirthdate(); // 比較対象カラムの指定
    });
});

外側クエリーでの絞り込みを忘れずに

絞り込み条件としては、あくまで単一のカラムだけで絞り込みをしているだけです。なので例えば、"一番若い正式会員" と言った場合でも、外側クエリーにも正式会員の条件を入れなければ、たまたま同じ生年月日で別のステータスの会員が紛れてしまいます。

e.g. ScalarConditionを使って一番若い正式会員(のレコード)を取得 @Java
cb.query().scalar_Equal().max(memberCB -> { // 一番若い正式会員
    memberCB.specify().columnBirthdate(); // 比較対象カラムの指定
    memberCB.query().setMemberStatusCode_Equal_Formalized(); // 絞り込み条件
});
cb.query().setMemberStatusCode_Equal_Formalized(); // 外側の絞り込み条件
e.g. 一番若い正式会員(のレコード)を取得するときのSQL @DisplaySql
...
  from MEMBER dfloc
 where dfloc.BIRTHDATE = (select max(sub1loc.BIRTHDATE)
                            from MEMBER sub1loc
                           where sub1loc.MEMBER_STATUS_CODE = 'FML'
       )
   and sub1loc.MEMBER_STATUS_CODE = 'FML'
...

関連テーブルのカラム

関連テーブル(many-to-one, one-to-one)のカラムも利用できます。

e.g. 会員セキュリティ情報を一番最近に更新した会員 @Java
cb.query().queryMemberSecurity().scalar_Equal().max(securityCB -> {
    securityCB.specify().columnUpdateDatetime(); // 一番最近に更新したセキュリティ
});

パーティション条件の追加

区切った中での最大とか

ScalarCondition を、とあるカラム単位で区切った中での条件にすることができます。 例えば、すべての会員の中で一番若い会員ではなく、会員ステータスごとに一番若い会員を検索するなど。

e.g. 会員ステータスごとに一番若い会員 @Java
cb.query().scalar_Equal().max(memberCB -> {
    memberCB.specify().columnBirthdate();
}).partitionBy(colCB -> { // カラム指定だけなので、Lambda引数名はcolCB
    colCB.specify().columnMemberStatusCode();
});
e.g. 会員ステータスごとに一番若い会員 @DisplaySql
...
  from MEMBER mb
 where mb.BIRTHDATE = (select max(nmb.BIRTHDATE)
                         from MEMBER nmb
                        where nmb.MEMBER_STATUS_CODE = mb.MEMBER_STATUS_CODE
       )
...

PartitionByでも外側クエリーの絞り込みを忘れずに

PartitionByを使ったときでも、サブクエリの中の絞り込み条件と、外側のクエリの絞り込み条件のつじつまを合わせる必要があります。 (もちろん、業務的にその必要がある場合のみですが、そのケースはわりと多いと想定されます)

例えば、会員ステータスごとに一番若い、会員名称に "vi" を含む会員という条件を実装するときに、"vi" を含まない同じステータスの会員でたまたま同じ生年月日の会員がいたら、ScalarConditionだけの条件だとそのレコードも検索される可能性があるため、 外側クエリーでも同じ条件で絞り込む必要があります。

e.g. 会員ステータスごとに一番若い、会員名称に "vi" を含む会員 @Java
cb.query().scalar_Equal().max(memberCB -> {
    memberCB.specify().columnBirthdate();
    memberCB.query().setMemberName_LikeSearch("vi", new LikeSe...);
}).partitionBy(colCB -> {
    colCB.specify().columnMemberStatusCode();
});
cb.query().setMemberName_LikeSearch("vi", new LikeSe...);

もし、その絞り込み条件が重複させるのに抵抗があるくらいわりとボリュームが多く、他でも使いそうな定型業務的な条件であれば、ArrangeQuery での再利用を使ってみるとよいでしょう。 (ここだけの条件であれば、同じクラスで arrangeXxx() メソッド化して再利用で)

ColumnQuery+DerivedReferrerでもできるが...

この場合、会員ステータスはテーブル区分値として存在しているため、この機能を利用しなくても ColumnQuery と DerivedReferrer を組み合わせて利用すれば同じことが実現できますが、Partitionを表現するテーブルが存在しない暗黙の区分値のカラムにおいては、こちらの機能でないと実現できません。

また、DerivedReferrer を利用した場合に比べて、こちらは結合なしで実現できるため、よりシンプルなSQLになります。 ただ、DerivedReferrer の方が関数による変換などオプション的な機能は豊富です。

導出カラムとPartitionカラムが別テーブルだと...

だがしかし、ScalarCondition は、制限として導出カラムとPartitionカラムが別テーブルのケースで利用できません。 なので、そのときは ColumnQuery + DerivedReferrer を使うことになります。(Partitionカラムがテーブル区分値であることが前提)

e.g. サービスランクごとに一番若い会員 @Java
cb.columnQuery(colCB -> {
    colCB.specify().columnBirthdate();
}).equal(colCB -> {
    colCB.specify().specifyMemberServiceAsOne().specifyServiceRank()
            .derivedMemberServiceList().max(serviceCB -> {
        serviceCB.specify().specifyMember().columnBirthdate();
    }, null);
});
e.g. 会員ステータスごとに一番若い会員 @DisplaySql
...
  from MEMBER mb
    left outer join MEMBER_SERVICE serv on mb.MEMBER_ID = ...
    left outer join SERVICE_RANK rank on serv.SERVICE_RANK_CO...
 where mb.BIRTHDATE = (select max(nmb.BIRTHDATE)
                         from MEMBER_SERVICE nserv
                           inner join MEMBER nmb on nserv.MEMB... 
                        where nserv.SERVICE_RANK_CODE = rank.SERVICE_RANK_CODE
       )
...

メソッド仕様

基本仕様

引数の指定
引数の SubQuery は必須です。
比較対象カラムの指定は一つ
比較対象カラムの指定は必ず一つだけです。指定が無い場合、指定があり過ぎる場合は例外です。
サブクエリのConditionBean
サブクエリの ConditionBean は、比較対象カラムと絞り込み条件だけの指定に利用するものです。 SetupSelect や OrderBy などサブクエリとして必要のない機能は呼び出してはいけません。

利用できる比較条件

利用できる比較条件は、以下の通りです。

scalar_Equal()
等値
scalar_NotEqual()
非等値
scalar_GreaterThan()
大なり
scalar_LessThan()
小なり
scalar_GreaterEqual()
大なりイコール
scalar_LessEqual()
小なりイコール

利用できる関数

利用できる関数は、以下の通りです。

max()
最大値
min()
最小値
sum()
合計値。数値のみ。
avg()
平均値。数値のみ。値が小数点になる可能性があります。

サポートされるテーブル

基点テーブルが単一の主キーである必要があります。(複合主キーはNG)