HAVINGとCASE WHEN
インターン先でSQLを書き始めて3週間ほどが経ちました。「スッキリ分かるSQL入門」をさらっと見た後(ドリルはやっていない)、有名な「達人に学ぶSQL徹底指南書」を読み始めました。
本書ではCASE WHEN句と自己結合、HAVING句やEXISTを特に推して説明しているのですが、読んでいる内に少し違和感のある文を見つけました。
それはCASE WHENを説明している章で
HAVING句で条件分岐させるのは素人のやること。プロはSELECT句で分岐させる
と書いているのです。
他ではHAVING句(2章分も)推しているのに、それをやるのは「素人」だと言っているのです。
私はちょっとイライラしたので、とりあえずHAVING句で説明している一章分を、CASE WHENで書き直しながら確認してみることにしました。
サンプルデータ
http://www.geocities.jp/mickindex/database/db_support_sinan.html#LocalLink-p1_4
に筆者が提供しているデータがサンプルデータの挿入コードがあります。今回試したのはその中の、「1-4.HAVING句の力」のデータです。
CREATE TABLE Graduates
(name VARCHAR(16) PRIMARY KEY,
income INTEGER NOT NULL);
INSERT INTO Graduates VALUES('サンプソン', 400000);
INSERT INTO Graduates VALUES('マイク', 30000);
INSERT INTO Graduates VALUES('ホワイト', 20000);
INSERT INTO Graduates VALUES('アーノルド', 20000);
INSERT INTO Graduates VALUES('スミス', 20000);
INSERT INTO Graduates VALUES('ロレンス', 15000);
INSERT INTO Graduates VALUES('ハドソン', 15000);
INSERT INTO Graduates VALUES('ケント', 10000);
INSERT INTO Graduates VALUES('ベッカー', 10000);
INSERT INTO Graduates VALUES('スコット', 10000);
サンプル(最頻値と中央値を求める)
name | income |
---|---|
サンプソン | 400,000 |
マイク | 30,000 |
ホワイト | 20,000 |
アーノルド | 20,000 |
スミス | 20,000 |
ロレンス | 15,000 |
ハドソン | 15,000 |
ケント | 10,000 |
ベッカー | 10,000 |
スコット | 10,000 |
このデータから最頻値を求めます。 上がHAVINGで下がCASEです。
SELECT income
FROM Graduates
GROUP BY income
HAVING COUNT(*) >= ALL ( SELECT COUNT(*)
FROM Graduates
GROUP BY income);
income
--------
10000
20000
select
income,
case when
count(*) >= all(select count(*) from graduates group by income) then count(*) else null end
from Graduates
group by income
income | case
--------+------
30000 |
400000 |
10000 | 3
20000 | 3
15000 |
caseでnull値を除外する方法が思いつきませんでした。もう1階層またぐことなく、null値を除外して表示する関数とか、方法を教えていただけたらうれしいです。でも該当箇所以外の情報も得られるから、これはこれで良いのかもしれない。
次に中央値を求めます。
SELECT AVG(DISTINCT income)
FROM (SELECT T1.income
FROM Graduates T1, Graduates T2
GROUP BY T1.income
HAVING SUM(CASE WHEN T2.income >= T1.income THEN 1 ELSE 0 END)
>= COUNT(*) / 2
AND SUM(CASE WHEN T2.income <= T1.income THEN 1 ELSE 0 END)
>= COUNT(*) / 2 ) TMP;
avg
--------------------
17500.000000000000
elect AVG(newIncome)
from(
SELECT
CASE WHEN
SUM(case when t2.income >= t1.income then 1 else 0 end) >= count(*) / 2
and SUM(case when t2.income <= t1.income then 1 else 0 end) >= count(*) / 2
THEN t1.income
else null
end as newIncome
from graduates t1, graduates t2
group by t1.income
) tmp
avg
--------------------
17500.000000000000
まとめ
確かにHAVINGで行える処理は、丸々同じ条件をSELECT内CASE WHEN句に書くことによって代替できそうですね。他のサンプルもいじってみましたが、殆ど同じ方法で代替可能でした。
とすると、どちらの方法で行うほうが良いのか、素人の私にはイマイチよく分からなくなってきた今日このごろです。なんとなくHAVINGで処理したほうが可読性は良い気がするので、最初はHAVINGで書こうと思いますが。
GROUP BYでグループ分けした後は、とりあえずHAVINGで処理しようとしたほうが好ましいのですかね? どなたかコメントしていただけると嬉しいです。