simon’s diary

思うがままに書き綴る。。。

SQLチューニングの条件分岐に関する備忘録

前回の記事の続きです。

simon.hatenadiary.com

SQLにおける分岐条件

CASE式を用いる方法と集合演算子UNIONを用いる方法があるが、基本は以下の二つの理由からCASE式を用いること。

  • CASE文の方がパフォーマンスが良い
  • SQL自体が簡潔に書ける
UNIONを使った条件分岐

SELECT item_name, year, price_tax_ex AS price

  FROM Items

  WHERE year <= 2001

UNION ALL

SELECT item_name, year, price_tax_in AS price

  FROM Items

  WHERE year >= 2002;

CASE式を使った条件分岐

SELECT item_name, year,

  CASE WHEN year <= 2001 THEN price_tax_ex

             WHEN year >= 2002 THEN price_tax_in END AS price

  FROM Items;

実行計画を確認するとUNIONのはテーブルに二回アクセスしているが、CASE文は一回しかアクセスしないので、二倍程度パフォーマンスに差が出る。

しかし、異なるテーブルの結果をマージするときはUNIONを使わなければならない。

UNION必須なケース

SELECT col_1

  FROM Table_A

  WHERE col_2 = 'A'

UNION ALL

SELECT col_3

  FROM Table_B

  WHERE col_4 = 'B';

また、UNIONを使ってうまく絞り込みの効くインデックスを利用できて、かつUNION以外の手段ではそうしたインデックスを利用できない場合、UNIONを利用したほうがパフォーマンスが良いこともある。

カットと集約

GROUP BY句:集約とカットの機能を持つ

GROUP BYを用いて集約操作を行った場合、SELECT句には以下の三つしか書くことができない。

  • 定数
  • GROUP BY句で指定した集約キー
  • 集約関数

SELECT id,

  CASE WHEN data_type = 'A' THEN data_1 ELSE NULL END AS data_1,

  CASE WHEN data_type = 'A' THEN data_2 ELSE NULL END AS data_2

  FROM NonAggTbl

  GROUP BY id;

上記のようなクエリは構文エラーが発生します。正しくは下記のクエリです。

SELECT id,

  MAX (CASE WHEN data_type = 'A' THEN data_1 ELSE NULL END) AS data_1,

  MAX (CASE WHEN data_type = 'A' THEN data_2 ELSE NULL END) AS data_2

  FROM NonAggTbl

  GROUP BY id;

PARTITION BY句:カットの機能のみ持つウィンドウ関数

PARTITION BYは集約機能をもたないので、GROUP BYと違い、SELECT句に普通の列を指定できる。

SELECT name, age,

  CASE WHEN age < 20 THEN '子供'

             WHNE age BETWEEN 20 AND 69 THEN '成人'

             WHNE age >= 70 THEN '老人'

             ELSE NULL END AS age_class,

  RANK() OVER (PARTITION BY CASE WHEN age < 20 THEN '子供'

                                                              WHEN age BETWEEN 20 AND 69 THEN '成人'

                                                                WHEN age >= 70 THNE '老人'

                                                                ELSE NULL END

                           ORDER BY age) AS age_rank_in_class

  FROM Persons

ORDER BY age_class, age_rank_in_class;