ManualOrder

概要

基本概念

指定された値(リスト)の順番通りに並べる設定をします。

例えば、会員ステータスに表示順(DisplayOrder)のようなカラムが存在しない場合(かつ、どうしてもカラム追加できない場合)でも、"正式会員、仮会員、退会会員" という決まった順番で並べて検索することができます。 また、仮会員だけは最初に並べて、それ以外の会員は単に更新日時で並べるというような、ある値だけを優先するソート要件がある場合などにも対応できます。

アプリでは、並べる順番の値のリストを指定することで、order by の中で case when 構文を使ってそのソート条件を実現します。 必ず OrderBy と組み合わせて指定します。

会話上では、まにゅあるおーだー と表現します。

実装方法

実装の流れ

まずは、OrderBy のメソッド呼び出しの直後に続けて、withManualOrder() を呼び出し、並べる順番の値のリスト(orderValueList)を引数に指定します。 (オーバーロードメソッドあり:後述)

e.g. 指定された値の順番通りに並べる実装手順 (Eclipseでコード補完) {MEMBER_STATUS_CODE} @Java
MemberCB cb = new MemberCB();

// 区分値カラムであれば、CDef 自体を値としてそのまま利用できる
List<CDef.MemberStatus> orderValueList = new ArrayList<CDef.MemberStatus>();
orderValueList.add(CDef.MemberStatus.Formalized);  // 正式会員
orderValueList.add(CDef.MemberStatus.Provisional); // 仮会員
orderValueList.add(CDef.MemberStatus.Withdrawal);  // 退会会員

cb.query().addOrderBy_MemberStatusCode_Asc().wi // .wi まで打ってメソッドを選択
--
cb.query().addOrderBy_MemberStatusCode_Asc().withManualOrder(orderValueList);
cb.query().addOrderBy_Birthdate_Desc().withNullsLast(); // その後のソート順を設定
cb.query().addOrderBy_MemberName_Asc();
e.g. 正式会員、仮会員、退会会員の順番で並べる @DisplaySql
select ...
  from MEMBER dfloc  
 order by 
   case
     when dfloc.MEMBER_STATUS_CODE = 'FML' then 0
     when dfloc.MEMBER_STATUS_CODE = 'PRV' then 1
     when dfloc.MEMBER_STATUS_CODE = 'WDL' then 2
     else 3
   end asc, dfloc.BIRTHDATE desc nulls last, dfloc.MEMBER_NAME asc

降順(Desc)を指定すると、指定されたリストの逆順に並びますが、必要性はあまりないでしょう。

リストで指定する値は、そのカラムが持つ値の一部でも構いません。その指定された値だけが優先して並び、それ以外は二番目以降のソート条件通りに並びます。

e.g. "Pixy" というアカウントの会員だけ最初に並べて、後は更新日時の降順 @Java
MemberCB cb = new MemberCB();
List<String> orderValueList = Arrays.asList("Pixy"); // 読み取り専用リストでもOK
cb.query().addOrderBy_MemberAccount_Asc().withManualOrder(orderValueList);
cb.query().addOrderBy_UpdateDatetime_Desc();
e.g. "Pixy" というアカウントの会員だけ最初に並べて、後は更新日時の降順 @DisplaySql
select ...
  from MEMBER dfloc  
 order by 
   case
     when dfloc.MEMBER_ACCOUNT = 'Pixy' then 0
     else 1
   end asc, dfloc.UPDATE_DATETIME desc

SpecifiedDerivedOrderBy との組み合わせ

SpecifiedDerivedOrderBy に対しても利用できますが、必要になる場面はあまり想定されません。

ManualOrderBean で細かい制御

ManualOrderBean を引数にとるオーバーロードメソッドがあります。 これを利用すると、もっと細かい制御が指定できるようになります。@since 0.9.8.2

例えば、24時間以内に更新されたデータを優先して並べるが、それより前のものに関しては別のカラムの値で並べる。 この場合、日付の大なり小なり比較が必要になりますが、ManualOrderBean で比較演算子を指定して条件を追加することができます。

e.g. 24時間以内に更新されたデータを優先して並べる {UPDATE_DATETIME} @Java
MemberCB cb = new MemberCB();
ManualOrderBean mob = new ManualOrderBean();
mob.when_GreaterThan(date24before); // 更新日時 > 24時間前
cb.query().addOrderBy_UpdateDatetime_Asc().withManualOrder(mob);
cb.query().addOrderBy_Birthdate_Desc().withNullsLast(); // その後のソート順を設定
cb.query().addOrderBy_MemberName_Asc();

一つのケース(when)に対して複数の条件を連結させることもできます。whenメソッドで一つ目の条件を追加した後に続けて and_ もしくは、or_ で二つ目以降の条件を追加します。ただし、三つ以上の条件を連結する際、一つのケース内で利用できる連結子(and/or)は一つです。and と or を組み合わせて利用することはできません。

e.g. 会員IDが 5 から 10 の会員を優先して並べる {MEMBER_ID} @Java
MemberCB cb = new MemberCB();
ManualOrderBean mob = new ManualOrderBean();
mob.when_GreaterEqual(5).and_LessEqual(10); // 5 から 10
cb.query().addOrderBy_MemberId_Asc().withManualOrder(mob);
cb.query().addOrderBy_Birthdate_Desc().withNullsLast(); // その後のソート順を設定
cb.query().addOrderBy_MemberName_Asc();

メソッド仕様

基本仕様

適用対象
ManualOrder は、直前の OrderBy に対してのみ適用されます。
nullの指定
nullが指定されたら例外です。また、リストの中の null 要素は無視されます。
空リストの指定
空リストが指定されたらその ManualOrder は無効です。もし、同じカラムに対して既に一度 ManualOrder を設定している場合は、その設定を上書きして、ManualOrder を無効化します。
ケースの条件値
ManualOrderBean の when メソッドの引数の条件値は必須です。null は例外です。

読み取り専用リストでも可

内部では指定されたリストに更新操作はしないので、読み取り専用リストも指定できます。よって、Arrays.asList() を使って、シンプルにリストを構築することもできます。

CDef型の利用

区分値カラムに関しては、CDef型(厳密には Classification 型)の値をそのまま利用できます。(SQL上ではコード値が利用されます)

UnionQuery と case when

DBMSに関わらず、UnionQuery を使った時の ManualOrder はサポートされません。

必ず OrderBy の後に

OrderBy のメソッド呼び出し直後以外のタイミングでは呼び出してはいけません。

同カラムに対する複数回呼び出し

一つのカラムに対して、ManualOrder を複数回呼び出しても上書きされるだけです。

サポートされる型

SQLの文法的に許される限り全ての型に対してサポートされます。

いざってとき技、でも概念を明確化

まず、会員ステータスのようなテーブルに表示順カラムがない場合は、カラムを追加する方が良いです。 また、例えば、特別な扱いをする "得意先の会社" を常に先に並べたいというような優先する値があるような場合も、そういう情報をDB上で持ってシンプルな OrderBy で実現できるようにする方が良いです。 それがどうしてもできないときの "いざってとき技" が ManualOrder です。(制限も幾つかあるので、使わないに越したことはないです)

ただ、こういう ManualOrder という概念が機能として存在することが大事だと考えています。 DB設計する上で忘れていた、そういう考慮を知らなかった、というような状態が少しでも少なくなればと。 また、"頻繁に扱うある特別なデータだけは常に先に並べられるようにする" という隠れやすい業務要件も忘れずに。 全てのデータに対してフラットな扱いをするとは限りません。DB構造ではフラットな扱いでも、 人から見るとデータごとに着目の濃度に違いがあることがあります。ソート設計をするときに、データの構造だけにとらわれず、 その辺も考慮できるとよりユーザにとって使いやすいものになります(もちろん、パフォーマンスや実装などの現実問題も考える必要ありますが)。 逆に、その辺を十分理解して、あらかじめDB構造に反映できれば一番良いでしょう。すると ManualOrder も不要で実装もシンプルになります。 適応できる場面は少ないかもしれませんが、そういう概念を知っておくことが、より良い業務仕様の策定につながると考えます。