[SQL] Where句とHaving句の違い

2016.07.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

PostgreSQL始めました。
SQLを学ぶ過程でWhere句とHaving句の違いがわからずに悩んだ時期があったので、その時感じたことを書いておきます。

環境

MacOS 10.10.5
PostgreSQL 9.4.5

Where句でもHaving句でも同じ結果が出るけど何が違うんだろう?

結論から言うと

Where句はselect句の結果からwhere句で指定した抽出条件を実行する
Having句はGroupBy句でグルーピングした結果からHaving句で指定した抽出条件を実行する

という違いがあります。

同じ結果が出ることを確認

PostgreSQLのチュートリアルにあるデータベースを使います。手を動かして確認したい方はこちらを見てデータベースを用意していただければと思います。

今回使用するカスタマーテーブルは以下のようになっています。

select
 * 
from
 customer
order by
 customer_id asc
;
 customer_id | store_id | first_name  |  last_name   |                  email                   | address_id | activebool | create_date |        last_update         | active 
-------------+----------+-------------+--------------+------------------------------------------+------------+------------+-------------+----------------------------+--------
           1 |        1 | Mary        | Smith        | mary.smith@sakilacustomer.org            |          5 | t          | 2006-02-14  | 2016-07-28 12:14:31.748732 |      1
           2 |        1 | Patricia    | Johnson      | patricia.johnson@sakilacustomer.org      |          6 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
           3 |        1 | Linda       | Williams     | linda.williams@sakilacustomer.org        |          7 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
           4 |        2 | Barbara     | Jones        | barbara.jones@sakilacustomer.org         |          8 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
           5 |        1 | Elizabeth   | Brown        | elizabeth.brown@sakilacustomer.org       |          9 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
           6 |        2 | Jennifer    | Davis        | jennifer.davis@sakilacustomer.org        |         10 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
           7 |        1 | Maria       | Miller       | maria.miller@sakilacustomer.org          |         11 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
           8 |        2 | Susan       | Wilson       | susan.wilson@sakilacustomer.org          |         12 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
           9 |        2 | Margaret    | Moore        | margaret.moore@sakilacustomer.org        |         13 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
          10 |        1 | Dorothy     | Taylor       | dorothy.taylor@sakilacustomer.org        |         14 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
          11 |        2 | Lisa        | Anderson     | lisa.anderson@sakilacustomer.org         |         15 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
          12 |        1 | Nancy       | Thomas       | nancy.thomas@sakilacustomer.org          |         16 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
          13 |        2 | Karen       | Jackson      | karen.jackson@sakilacustomer.org         |         17 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
          14 |        2 | Betty       | White        | betty.white@sakilacustomer.org           |         18 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
          15 |        1 | Helen       | Harris       | helen.harris@sakilacustomer.org          |         19 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
          16 |        2 | Sandra      | Martin       | sandra.martin@sakilacustomer.org         |         20 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      0
          17 |        1 | Donna       | Thompson     | donna.thompson@sakilacustomer.org        |         21 | t          | 2006-02-14  | 2016-07-28 12:14:29.088828 |      1
(599 row) 以下省略

ではWhere句を使ってもHaving句を使っても同じ結果を出ることを見てみます。
今回はstore_idが1の店舗ユーザーの合計を抽出するSQLを書いてみます。

Where句を使って抽出

select 
 store_id
,count(customer_id) as customer_number 
from 
 customer 
where 
 store_id = 1 
group by
 store_id
;
 store_id | customer_number 
----------+-----------------
        1 |             326

Having句を使って抽出

select 
 store_id
,count(customer_id) as customer_number 
from 
 customer 
group by
 store_id 
having 
 store_id =1
;
 store_id | customer_number 
----------+-----------------
        1 |             326

上の2つのSQLの違いはハイライトしている2行ずつです。
Where句を使ってもHaving句を使っても同様の結果を得ることができますね。

では内部的にも同じ動作をしているかというとそうではありません。

SQLが実行される順序は以下のようになっており

FROM → WHERE → GROUPBY → HAVING → SELECT → ORDERBY

GroupByでグルーピングする前に抽出するのがWhere句で
GroupByでグルーピングした後に抽出するのがHaving句になります。

もう少し詳しく見てみます。

Where句を使う場合

まずテーブルWhere句で指定された抽出条件(今回のケースではstore_id = 1)を実行します。

select 
 store_id 
from 
 customer
where
 store_id = 1
;
 store_id 
----------
        1
        1
        1
        1
        1
        1
        1
        1
        1
        1
        1
        1
        1
        1
(326 row) 以下省略 ※GroupByを実行していないので、Select句のCount(*)は実行できないため省略

store_id = 1 のレコードを抽出しています。その結果に対してGroupBy句を実行します。

select
 store_id
,count(customer_id) as customer_number 
from
 customer 
where 
 store_id = 1 
group by
 store_id;
 store_id | customer_number 
----------+-----------------
        1 |             326

Having句の場合

From句の後にGroupBy句が実行されます。

select
 store_id
,count(customer_id) as customer_number 
from
 customer 
group by 
 store_id
;
 store_id | customer_number 
----------+-----------------
        1 |             326
        2 |             273

GroupBy句でstore_idごとにグルーピングされています。
その結果に対してHaving句で指定した条件(今回の場合はstore_id = 1)で抽出します。

select
 store_id
,count(customer_id) as customer_number 
from
 customer 
group by
 store_id 
having
 store_id =1
;
 store_id | customer_number 
----------+-----------------
        1 |             326

同じ結果は得られますが、動作順序が違うことがわかりますね。

最後に

SQLの実行される順番がわかれば、どの句を使えばいいのかがわかるようになります。
こちらの記事と合わせて書き順も覚えていただければ幸いです。