5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SQLのHAVING句はSELECT内CASE WHENで置き換えられる

Last updated at Posted at 2017-05-13

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です。

having.sql

SELECT income
FROM Graduates
GROUP BY income
HAVING COUNT(*) >= ALL ( SELECT COUNT(*)
                             FROM Graduates
                         GROUP BY income);
having.sqlの結果
income 
--------
  10000
  20000
case.sql

select
income,
case when 
     count(*) >= all(select count(*) from graduates group by income) then count(*) else null end
from Graduates
group by income
case.sqlの結果
 income | case 
--------+------
  30000 |     
 400000 |     
  10000 |    3
  20000 |    3
  15000 |     

caseでnull値を除外する方法が思いつきませんでした。もう1階層またぐことなく、null値を除外して表示する関数とか、方法を教えていただけたらうれしいです。でも該当箇所以外の情報も得られるから、これはこれで良いのかもしれない。

次に中央値を求めます。

having.sql

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;
having.sqlの結果
        avg         
--------------------
 17500.000000000000
case.sql

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
case.sqlの結果
        avg         
--------------------
 17500.000000000000

まとめ

確かにHAVINGで行える処理は、丸々同じ条件をSELECT内CASE WHEN句に書くことによって代替できそうですね。他のサンプルもいじってみましたが、殆ど同じ方法で代替可能でした。

とすると、どちらの方法で行うほうが良いのか、素人の私にはイマイチよく分からなくなってきた今日このごろです。なんとなくHAVINGで処理したほうが可読性は良い気がするので、最初はHAVINGで書こうと思いますが。

GROUP BYでグループ分けした後は、とりあえずHAVINGで処理しようとしたほうが好ましいのですかね? どなたかコメントしていただけると嬉しいです。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?