SELECT
SELECT文はデータを取得するために用いる命令文です。テーブルを引数にテーブルを返す関数みたいなイメージです。SELECTのクエリ結果は0行以上のテーブルになります。
単純クエリ
まずは、シンプルなクエリから説明します。
SELECT <式のリスト> FROM <テーブル名> [WHERE <述語>];
SELECT句は、クエリ結果として出力する行を形成するための<式のリスト>です。式に書けるのは列名、計算式、定数などです。学生テーブルからSELECTしたい場合、「age」「age + 2」「1」などがSELECT句に記述できます。WHERE句は、検索対象の行を絞り込むための<述語> (検索条件)です。述語とは、真偽値を返す関数を指します。例えば、age = 20やage >= 19 AND age <= 22などです。
追加で、SELECTの亜種を1つ紹介します。SELECT DISTINCTです。DISTINCTは、クエリ結果(SELECT句の処理結果)から重複行を除外する修飾子です。クエリの出力を制御する役割といえます。
ここまでに学んだことを応用すると、以下のようなクエリが書けます。
ORDER BY&LIMIT
DISTINCT以外にもクエリの出力を制御できる構文要素が存在します。1つ目は、ORDER BY句です。クエリ結果を任意の列で昇順にソートすることができます。デフォルトの並び順は、昇順(ASC)ですが、降順(DESC)に変更することもできます。さらに、複数の列を基準に並び替えることも可能です。例のように、最初に年齢順で並び替え、次に名前順で並び替える(年齢別に氏名を辞書順にする)ことができます。
もう1つは、LIMIT句です。クエリ結果を最初の <整数> 行に絞り込むことができます。ORDER BY句と一緒に使われます。理由は、クエリ結果の順序が非決定的になるのを避けるためです。
集約関数
ここでSELECT句に書けるもう1つのものを紹介します。集約関数です。これは複数行の列の値を集計し1つの値を返す関数です。集約とは、複数行を1行にまとめるという意味です。集約関数には、AVG、 SUM、 COUNT、 MAX、 MINなどが含まれます。構文は、関数名(<値式>)です。下の例では、AVGを使って、計算機科学を専攻している学生の年齢を集計し、平均年齢を出力しています。
COUNTを使ったクエリもみてみましょう。COUNTはレコードを数える集約関数です。下の例を見ると、COUNTの書き方はさまざまですが、全て同じ値を返しています。COUNT(*)とCOUNT(定数)は全行数を数えます。COUNT(列名)はNULLを除外した行数を数えます。今回の例では、ageが不明な学生(レコード)が存在しないので、クエリ結果が同じになります。
複数の集約関数を1つのクエリで使うこともできます。
COUNTは以下のように、DISTINCTと合わせて使うこともできます。重複値を排除した上で集計がされるようになります。他にも、SUMやAVGがDISTINCTをサポートしています。
SELECT COUNT(DISTINCT name) FROM student;
GROUP BY
ここまで集約関数で全学生に対して集計をしてきました。しかしながら、集計というのは、しばしば、グループごとに行いたいもので、その時に利用するのがGROUP BYです。構文は、GROUP BY <列のリスト(集約キー)>です。テーブルを集約キーでグループ分けし、グループ単位で集計することができます。つまり、GROUP BYとは、グルーピングと集約を足し合わせたものです。GROUP BYのクエリ結果の行数は、必ずユニークなグループの数になります。GROUP BY句の使用時にSELECT句に書けるものは、①GROUP BY句で指定した集約キー、②集約関数、③定数になります。
例を見ていきます。このクエリは、学生の専攻別に、平均年齢と学生の数を算出するものです。右の楕円の図は、学生テーブルを学生の集合と見て、学生の専攻毎に集約キーでグループ分け(カット)したイメージです。
ここで、GROUP BYを使う上で重要な、個人と集団の属性の違いについて考えてみます。個人の属性とは、例えば、Aさんの年齢などが含まれます。一方、集団の属性は、統計的な属性を指し、例えば、チームXの平均年齢などです。GROUP BYを使うということは、個人の属性(列)は気にせず、集団の属性だけに関心があるということです。GROUP BYを使用する場合は、頭を集合指向に切り替えましょう。
HAVING
HAVINGは、検索対象のグループを絞り込みたい時に利用します。条件に含められるものは、GROUP BY句で指定した集約キーまたは集約関数です。HAVINGはGROUP BY句と一緒にしか使えません。
評価順序
実は、SELECT文には以下の図のような論理的な評価順序(実行順序)があります。ここでは、今まで我々が何となく頭の中で行ってきた、「このクエリが最終的に何を出力するか」を考えるプロセスをより論理的なものに昇華していきたいと思います。
これまで学んだことをふんだんに使用した以下のクエリを例に説明していきます。この集計結果に意味があるかは一旦忘れてください。
SELECT major, AVG(age) AS avg_age FROM student WHERE age <= 20 GROUP BY major HAVING COUNT(*) >= 10 ORDER BY avg_age DESC LIMIT 1;
最初に評価されるのは、FROMです。FROMが評価されると、検索対象のテーブル、学生テーブル(全ての学生レコード)が手に入ります。続いて、WHERE句では、得られた結果を20歳以下の学生レコードだけに絞ります。
次に、GROUP BYが登場します。ここからは頭を集合指向に切り替えましょう。20歳以下の学生集合を専攻ごとにグルーピング(カット)して、HAVINGで検索対象のグループを学生数が10以上のものに絞ります。
ここからは、ここまでで得られたデータをもとに、何を出力するかを選択・制御します。SELECTによって、出力対象として専攻と平均年齢を選択します。SELECT句にあるASは、エイリアスをつける際に利用されます。そして、ORDER BYでその出力を平均年齢の降順に並び替え、LIMITで平均年齢が最も高いレコードに絞って、ユーザーに最終的なクエリ結果を返します。
SELECT文を書くとき、理解するときは、この評価順序に従って考えてみましょう。一見複雑かもしれませんが、やっていくうちに少しずつ慣れていくと思います。
補足ですが、この評価順序はあくまで論理的なもので、実際の評価順序はDBMSが決定します。また、製品によって、評価順序は異なってきます。
まとめ
本記事では、RDBMSのクエリ言語であるSQLの文法、特に、DDLのCREATEとDMLのSELECTを中心に学びました。ここまでの内容を理解することで、制約付きのテーブル作成と、単一テーブルからのデータ取得はできます。次回は、複数テーブルのデータを扱いたい時に利用する、結合(JOIN)クエリを扱います。また、トランザクションについても紹介する予定です。