図3●集約関数はNULL値の扱いに注意<BR>SUM(合計),AVG(平均)などの集約関数は,引数で指定したカラムにNULL値が含まれる場合,NULL値のレコードを除いて計算する。(1)SUM(合計)では問題は起きにくいが,(2)AVGや(3)COUNTはNULL値のレコードが除外されるため注意が必要である。AVG関数でNULL値のレコードを含めたい場合,(2)'のようにNVL関数でNULL値を0(ゼロ)に置き換える。また,COUNT関数では(3)'のようにCOUNT(*)と記述する
図3●集約関数はNULL値の扱いに注意<BR>SUM(合計),AVG(平均)などの集約関数は,引数で指定したカラムにNULL値が含まれる場合,NULL値のレコードを除いて計算する。(1)SUM(合計)では問題は起きにくいが,(2)AVGや(3)COUNTはNULL値のレコードが除外されるため注意が必要である。AVG関数でNULL値のレコードを含めたい場合,(2)'のようにNVL関数でNULL値を0(ゼロ)に置き換える。また,COUNT関数では(3)'のようにCOUNT(*)と記述する
[画像のクリックで拡大表示]
図4●GROUP BY句とHAVING句の動き&lt;BR&gt;GROUP BY句は集約関数と一緒に利用し,指定したカラムの値が同じレコードを同一グループとして集計する。一方のHAVING句はGROUP BY句と一緒に利用し,GROUP BY句でグループ化されたレコードから指定した条件に合うレコードを抽出する
図4●GROUP BY句とHAVING句の動き<BR>GROUP BY句は集約関数と一緒に利用し,指定したカラムの値が同じレコードを同一グループとして集計する。一方のHAVING句はGROUP BY句と一緒に利用し,GROUP BY句でグループ化されたレコードから指定した条件に合うレコードを抽出する
[画像のクリックで拡大表示]
図5●GROUP BY句の利用上の注意点&lt;BR&gt;(1)GROUP BY句を付けたSELECT文の検索項目には制限があり,GROUP BY句の指定カラムと集約関数しか指定できない。逆に,(2)GROUP BY句で指定したカラムを検索項目に指定しないと,意味不明な結果になる
図5●GROUP BY句の利用上の注意点<BR>(1)GROUP BY句を付けたSELECT文の検索項目には制限があり,GROUP BY句の指定カラムと集約関数しか指定できない。逆に,(2)GROUP BY句で指定したカラムを検索項目に指定しないと,意味不明な結果になる
[画像のクリックで拡大表示]
図6●HAVING句の利用上の注意点&lt;BR&gt;HAVING句はWHERE句で置き換えることができる場合がある。図は,同じ結果を検索するHAVING句を利用したSELECT文((1))と,HAVING句を使わないでWHERE句を利用したSELECT文((2))の例である。同じ結果を検索するが,実行性能は(2)WHERE句を利用したSELECT文の方が高い。WHERE句を使った場合はインデックス検索であるが,HAVING句を使った場合は全件検索になる
図6●HAVING句の利用上の注意点<BR>HAVING句はWHERE句で置き換えることができる場合がある。図は,同じ結果を検索するHAVING句を利用したSELECT文((1))と,HAVING句を使わないでWHERE句を利用したSELECT文((2))の例である。同じ結果を検索するが,実行性能は(2)WHERE句を利用したSELECT文の方が高い。WHERE句を使った場合はインデックス検索であるが,HAVING句を使った場合は全件検索になる
[画像のクリックで拡大表示]

●集約関数
NULLの扱いに注意

 RDBMSには,数値カラムの合計値計算(SUM)や平均値計算(AVG)を行う集約関数が用意されている。例えば図3[拡大表示]の販売テーブルにおいて「値引き」の合計値を求める場合,

SELECT SUM(NEBIKI)
FROM HANBAI_TBL ;

とすると,NEBIKI(値引き)の合計金額を求めることができる(図3(1))。合計値や平均値のほか,最大値(MAX),最小値(MIN),レコード数の算出(COUNT)などの関数がある。集約関数を利用する際は,集計対象にNULL値がある場合に注意が必要だ。

NULL値のレコードは対象外になる

 集約関数の集計対象にNULL値が含まれる場合,集約関数はNULL値を除いて計算する。合計値計算や最大値,最小値の関数ではNULL値があってもなくても結果は変わらないが,平均値(AVG)とレコード数の算出(COUNT)の場合はNULLの有無で結果が変わる。

 図3の販売テーブルにある「値引き」データの平均値を求める場合,「AVG(NEBIKI)」とすれば計算できる(図3(2))。だがAVG関数はNULL値のレコードを除外するため,値引きの合計を全レコード数で割った値とは異なる結果となる。図3(2)の場合は非NULL値のレコードが2レコードなので,値引きの合計値を2で割った結果となる。もし全部のレコード数で割った平均値を求めたい場合は,NULL値を0(ゼロ)に置き換えてAVG関数を使う。具体的にはNVL関数*を使って,「AVG(NVL(NEBIKI,0))」とする(同(2)')。

 レコード数の算出関数(COUNT)も同様である。図3(3)のように「COUNT(NEBIKI)」とすると,NEBIKIの値がNULL値のレコードを除外する。全レコード数を算出する場合は「COUNT(*)」とする(同(3)')。

●GROUP BY句,HAVING句
便利であるが乱用してはならない

 SQL文にはレコードをグループ化する「GROUP BY句」や,GROUP BY句でグループ化したレコードからさらに条件抽出する「HAVING句」がある。これらを使えば1つのSQL文で複雑な条件を処理することができるが,使い方や実行性能に注意すべき点がある。

レコードをグループ化して条件で抽出する

 まず,GROUP BY句とHAVING句の基本的な動きを確認しておこう。図4[拡大表示]上のSQL文は図3の販売テーブルに対するもので,「商品コードごとに集計した金額の合計が50万円以上の商品の『商品コード』『数量の合計』『金額の合計』を算出する」SQL文である。商品コード(SYOHIN_CD)ごとの数量(SUURYO)と金額(KINGAKU)の合計値を計算する必要があるが,そのような場合,指定カラムの値でレコードをグループ化する「GROUP BY句」を使うと便利である。

SELECT SYOHIN_CD,SUM(SUURYO),SUM(KINGAKU)
FROM HANBAI_TBL
GROUP BY SYOHIN_CD ;

と記述すると,SYOHIN_CD(商品コード)ごとにレコードをグループ化する。「SUM(SUURYO)」はSYOHIN_CD(商品コード)ごとの数量(SUURYO)の合計値で,「SUM(KINGAKU)」はSYOHIN_CD(商品コード)ごとの金額(KINGAKU)の合計値を計算する。

 HAVING句は,GROUP BY句でグループ化したレコードから所定の条件で抽出したい場合に利用する。図4の例では,「商品コードごとに集計した金額が50万円以上の商品」であるため,先述したSQL文に「HAVING SUM(KINGAKU)>=500000」を追加すると,目的のレコードが抽出される。

GROUP BY句と出力カラムはペアになる

 GROUP BY句を利用した場合,SELECT*の後ろに記述するカラムは,(1)集約関数か(2)GROUP BY句で指定したカラムになる。この点はミスしやすいので注意が必要である。

 まず多いミスが,GROUP BY句で指定していないカラムをSELECTの後ろに記述する場合である。図5[拡大表示](1)のSQL文の場合,1行目が構文エラーになる。エラーの原因は,SELECTの後ろに「KUBUN」を記述していることである。GROUP BY句に「SYOHIN_CD」はあるが「KUBUN」はない。「SUM(SUURYO)」は集約関数なので問題ない。実際,このSQL文の動きを考えた場合,GROUP BY句で指定した「SYOHIN_CD」カラムでグループ化するため,SYOHIN_CD以外のカラム値は複数の値を持つことになり,集計しないと出力できない。

 次は,GROUP BY句で指定しているにもかかわらず,SELECTの後ろに記述しない場合である。図5(2)のSQL文はエラーにはならないが,出力結果の意味が不明になる。「GROUP BY KUBUN,SYOHIN_CD」としているので,KUBUN(商品区分)とSYOHIN_CD(商品コード)の2つのカラム値でグループ化し,集計している。しかし出力結果に「KUBUN」しか指定していないため,結果をどのように読み取ればよいかが分からない。図5(2)の場合は,

SELECT KUBUN,SYOHIN_CD,SUM(SUURYO)
FROM HANBAI_TBL
GROUP BY KUBUN,SYOHIN_CD ;

とすれば,商品区分(KUBUN)と商品コード(SYOHIN_CD)ごとの集計値であることが明らかになる。

HAVING句の安易な利用は禁物

 GROUP BY句の結果をさらに抽出するHAVING句は便利である半面,SQL文の実行性能の観点では最適な方法でない場合があるので注意が必要である。

 「SYOHIN_CD(商品コード)が'001'である商品の『商品コード』『数量の合計』『金額の合計』を取り出す」SQL文を考えてみよう。GROUP BY句とHAVING句を利用すれば図6[拡大表示](1)のように,

SELECT SYOHIN_CD,SUM(SUURYO),SUM(KINGAKU)
FROM HANBAI_TBL
GROUP BY SYOHIN_CD
HAVING SYOHIN_CD = '001' ;

というSQL文になる。このSQL文は,販売テーブルをSYOHIN_CDの値でグループ化して集計し,その後,SYOHIN_CDが'001'のレコードだけを抽出する。

 同じ結果を導き出すSQL文は,HAVING句を使わずにWHERE句を使って記述できる。具体的には図6(2)のように,

SELECT SYOHIN_CD,SUM(SUURYO),SUM(KINGAKU)
FROM HANBAI_TBL
WHERE SYOHIN_CD = '001'
GROUP BY SYOHIN_CD ;

というSQL文になる。後者のSQL文は,販売テーブルからSYOHIN_CDが'001'のレコードを絞り込み,その後で集計する。

 どちらのSQL文でも同じ結果になるが,実行性能には差がある。販売テーブルにはSYOHIN_CDにインデックスが付いているのだが,図6(1)では全レコードを読み取る必要があるのでインデックスを利用しない。一方図6(2)のSQL文ではWHERE句の条件でレコードを絞り込む際にインデックスを利用できる。インデックスが利用できる分,HAVING句を使わない図6(2)のSQL文の方が実行性能が高い。HAVING句を使う場合は,WHERE句などで効率よく記述できないかを検討するようにしよう。

◇            ◇            ◇

 次回は,副問い合わせやビューを利用する際に注意すべき点を説明する予定である。


玉川 敏一
シーズ・ラボ ITソリューション部 セクションマネージャー