Hibernate 入門記 問い合せ その2 集計関数,継承,where

まずは昨日へぼへぼだった外部結合について.

from study.Magazine magazine left outer join magazine.model

HSQLDB のバージョンを 1.7.1 にもどしたところ,正しい結果を得ることが出来ました.

[CanCam(Shogakukan):[Yu Yamada(20), Asami Usuda(19), Yuri Ebihara(24)], Yuri Ebihara(24)]
[CanCam(Shogakukan):[Yu Yamada(20), Asami Usuda(19), Yuri Ebihara(24)], Yu Yamada(20)]
[CanCam(Shogakukan):[Yu Yamada(20), Asami Usuda(19), Yuri Ebihara(24)], Asami Usuda(19)]
[JJ(Kobunsha):[], null]
[ViVi(Kodansha):[Jun Hasegawa(18)], Jun Hasegawa(18)]
[Ray(Shufunotomo):[Karina(20)], Karina(20)]

これですよ.これが左外部結合というものですよ.
ちなみに 1.7.2 は 1.7.2.3 と同じでした.1.7.2.2 は試していませんが,きっと同じではないかと.
ということで,しばらく 1.7.1 でいくことにします.


問い合せ第二回目の今日は「11.5. Aggregate functions」から.
集計関数とか集約関数とか呼ばれるものですね.リファレンスには次のものが掲載されています.

  • avg(...)
  • sum(...)
  • max(...)
  • min(...)
  • count(*)
  • count(...)
  • count(distinct ...)
  • count(all ...)

ふむ.
点々の部分には,オブジェクトやプロパティを書けるようです.
まずは軽く試してみましょう.昨日のテーブルおよびサンプルコード,それにデータをそのまま使います.

select 
    count(*), min(model.age), max(model.age), avg(model.age) 
from 
    study.Model model

その実行結果.

[6, 18, 26, 21.0]

6人の平均年齢 21 歳かぁ.結構若かったのね.


group by を指定することも出来るそうです.

select 
    magazine.name, count(*), min(model.age), max(model.age), avg(model.age) 
from 
    study.Model model group by model.magazine

その実行結果.

net.sf.hibernate.QueryException: 
    could not resolve property: magazine of: study.Model [
        select magazine.name, count(*), min(model.age), 
            max(model.age), avg(model.age) 
        from study.Model model group by model.magazine]
...

ぐはぁっ,Model には雑誌への関連がありませんでした.(;_;)
外部キーはモデルのテーブルにあるのになぁ.もどかすぃ...
関連を双方向にしておけばよかったよ.しくしくしく.
ということで,雑誌ベースに変更.

select 
    magazine.name, count(*), min(model.age), max(model.age), avg(model.age) 
from 
    study.Magazine magazine inner join magazine.model model 
group by 
    magazine

その実行結果.

[CanCam, 3, 19, 24, 21.0]
[ViVi, 1, 18, 18, 18.0]
[Ray, 1, 20, 20, 20.0]

ふむふむ.いい感じ♪


distinct と all も指定できるそうです.
まずは distinct.

select 
    count(distinct model.age), min(model.age), max(model.age), avg(model.age) 
from 
    study.Model model

その実行結果.

[5, 18, 26, 21.0]

モデルは 6 人なのですが,優と香里奈は同い年なので最初の項目が 5 になってます.おっけー.
次に all.

select 
    count(all model.age), min(model.age), max(model.age), avg(model.age) 
from 
    study.Model model

その実行結果.

java.sql.SQLException: 
    Unexpected token: ALL in statement [
        select count(all model0_.age) as x0_0_, min(model0_.age) as x1_0_, 
            max(model0_.age) as x2_0_, avg(model0_.age) as x3_0_ 
        from Model model0_]
...

ぐはぁっ,HSQLDB では all は指定できないようです.
all って何も指定しないときと同じだよね? だったよね? だったら別に指定できなくてもいいや.


なぜかここで,集約関数の中ではなくて普通に SELECT 句に DISTINCT を指定する例が掲載されています.
なので,ちょっとやってみますか.

select distinct model.age from study.Model model

その実行結果.

18
19
20
24
26

ふむ.
ついでに昨日の fetch join も試してみましょう.

select 
    distinct magazine 
from 
    study.Magazine magazine 
        left outer join fetch magazine.model

その実行結果.

CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]
CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]
CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]
JJ(Kobunsha):[]
ViVi(Kodansha):[Jun Hasegawa(18)]
Ray(Shufunotomo):[Karina(20)]

ぐはぁっ.(ToT)
なぜ? なぜにそうなる? distinct しているのにどうして CanCam が3回返ってくるわけ?
Hibernate が実行した SQL を見ると...

select 
    distinct magazine0_.id as id0_, model1_.id as id1_, magazine0_.name as name0_, 
    magazine0_.publisher as publisher0_, model1_.name as name1_, model1_.age as age1_, 
    model1_.magazine as magazine__, model1_.id as id__ 
from 
    Magazine magazine0_ 
        left outer join Model model1_ on magazine0_.id=model1_.magazine

そういうことかぁ.雑誌だけじゃなくてモデルを含めた行に distinct が効いちゃっているのですね.参ったな...
うーむ,

from study.Magazine

と同じ結果を返しながら,一回の SQL で雑誌もフェッチしてしまう HQL は書けないのだろうか? 残念!!!!
次行きますよ! 次,次!! (エビちゃん風)


次は「11.6. Polymorphic queries」です.
しまった,サンプルでは継承使ってなぁーい!! 残念!!!!
でも大丈夫です.こんなこともできるそうです.

from java.lang.Object

その実行結果.

Sayo Aizawa(26)
Yuri Ebihara(24)
Yu Yamada(20)
Asami Usuda(19)
Jun Hasegawa(18)
Karina(20)
CanCam(Shogakukan):[Asami Usuda(19), Yu Yamada(20), Yuri Ebihara(24)]
JJ(Kobunsha):[]
ViVi(Kodansha):[Jun Hasegawa(18)]
Ray(Shufunotomo):[Karina(20)]

全ての永続クラスは Object のサブクラスなので,この問い合せで全ての永続オブジェクトを取得することが出来ます.
FROM 句に interface を指定すると,その interfaceimplements した全ての永続クラスのインスタンスを取得することが出来るそうです.
便利そうですが,裏では永続クラスごとに複数の SQL が発行されることになるので注意が必要です.
また,問い合せ結果は,それぞれの永続クラスごとに発行された SQL の結果を単純につないだものになるようです.
SQL を発行する順序は指定できないようなので,ORDER BY を指定しても意図したとおりにならないとのこと.
例えば雑誌もモデルも name というプロパティを持っているので,

intervade Named {
    String getName();
}

のような interface を用意しておけば,

from Named named order by named.name

のような問い合せが出来ますが,その結果は例えば前半にモデルが名前順で,後半は雑誌が名前順で,となってしまうということです.
あまり使うとは思えないので,どうでもよさげ.


次は「11.7. The where clause」です.
当然ながら,WHERE 句を使うことが出来ます.

from study.Model model where model.age > 20

その実行結果.

Sayo Aizawa(26)
Yuri Ebihara(24)

あらら,二十歳以上は二人しかいないのかぁ.


FROM 句で明示的に結合しなくても,WHERE 句の条件で関連を辿ると勝手に結合 (inner join) してくれるようです.

from study.Magazine magazine where magazine.model.age > 20

その実行結果.

net.sf.hibernate.QueryException: 
    expecting 'elements' or 'indices' after: age [
        from study.Magazine magazine where magazine.model.age > 20]

ぐはぁっ,何が起きたわけ!?
いろいろ試行錯誤したのですが,どうにもうまくできません.
age の後に期待されている 'elements' or 'indices' って??
どうやら,雑誌にとってモデルは対多関連であるため,この問い合わせでは magzin.model.age が集合になってしまうのでダメみたいです.対 1 関連か,もしくはリストやマップなど,要素を一つに絞れるものでないとダメみたい.
しかし,今のサンプルでは対 1 関連もリストも使っていません.残念!!!!
うーみゅ,とりあえず,宿題にしておきます... 心より恥じる.
次行きますよ! 次,次!! (エビちゃん風)


IS NULL とか IS NOT NULL とかも指定できるようです.

from 
    study.Magazine magazine 
        left outer join magazine.model model 
where 
    model is null

その実行結果.

[CanCam(Shogakukan):[Asami Usuda(19), Yuri Ebihara(24), Yu Yamada(20)], null]
[JJ(Kobunsha):[], null]
[ViVi(Kodansha):[Jun Hasegawa(18)], null]
[Ray(Shufunotomo):[Karina(20)], null]

あれれ? JJ だけになるはずだったのに?
うーみゅ,HSQLDB めぇ,左外部結合だといっているのにまた同じ過ちをしやがったな...
このとき Hibernate が発行した SQL は次のもの.

select 
    magazine0_.id as id0_, model1_.id as id1_, magazine0_.name as name0_, 
    magazine0_.publisher as publisher0_, model1_.name as name1_, model1_.age as age1_ 
from 
    Magazine magazine0_ 
        left outer join Model model1_ on magazine0_.id=model1_.magazine 
where 
    (model1_.id is null )

別におかしくないような?
でも,この SQL を実行すると,なぜか全ての雑誌と NULL だらけのモデルの組が返ってきます.
試しに WHERE 句を外してみると,ちゃんとした結果が返ってきます.
WHERE 句を付けると結果セットに行が追加されるなんて!! そんなのあり得なぁ〜い!! よって,切腹!!!!
そうかぁ,1.7.1 なら大丈夫ってわけじゃないんだぁ.1.7.1 でも 1.7.2 でも,HSQLDB の外部結合はだめだめ!! 間違いない.


気を取り直して.
小文字の id というプロパティは,永続オブジェクトの ID プロパティを表すそうです.
ということは,ID プロパティ以外で id という名前のプロパティがあったらやばそう.どっちが優先されるんだろ? 僕はいい子なのでそんなことしませんけど.
念のためお試し.

from study.Model model where model.id = 0

その実行結果.

Sayo Aizawa(26)

ふむ.


この「11.7. The where clause」はもう少し続くのですが,継承やコンポジションなどを使っていないと試せない感じ.
なので,残りとさっきの宿題は次回ということで.心機一転,サンプルも新たに再スタートします!!!!
でも,モデルがネタであることに変わりはないはず.なんせこの日記のメインですから.残念!!!!