SQLの知識は、データを扱う仕事では欠かせない基本スキルです。ただ、普段から書いていても、NULLの扱いや処理の順序といった細かな仕様は意外とあいまいなままになりがちです。この記事では、集計や結合、ウィンドウ関数、インデックス、制約など、実務でよく登場するSQLの重要ポイントを10問のクイズにまとめました。初心者の方は基礎の確認に、経験者の方は知識の総点検にぜひ活用してください。解説も付いているので、間違えた問題は理解を深めるきっかけになります。
Q1 : テーブルにインデックスを作成した場合に生じる可能性のあるデメリットとして、最も適切なものはどれですか?
正解は、更新処理が遅くなることがある、です。インデックスは検索を高速化する仕組みですが、データの追加、更新、削除が行われるたびにインデックス自体も更新しなければならないため、書き込み処理の負荷が増えます。また、インデックスを保持するための記憶領域も余分に必要になります。そのため、検索条件によく使われる列に絞って作成するのが基本で、むやみに数を増やすのは避けるべきです。なお、結果の正確性や制約には影響しません。
Q2 : 外部キー制約(FOREIGN KEY)の主な役割として、正しいものはどれですか?
正解は、参照先に存在しない値の登録を防ぎ、参照整合性を保つ、です。たとえばordersテーブルのcustomer_id列にcustomersテーブルのidを参照する外部キーを設定すると、customersに存在しない顧客IDで注文を登録することができなくなります。また、注文が紐づいている顧客を削除しようとした場合の動作も、ON DELETE CASCADEなどで制御できます。NULLの禁止はNOT NULL制約、重複の禁止はUNIQUE制約の役割です。
Q3 : LIKE 'A_C' というパターンに一致する文字列はどれですか?
正解はABCです。LIKE演算子のワイルドカードのうち、アンダースコア(_)は任意の1文字、パーセント(%)は0文字以上の任意の文字列に一致します。したがってA_Cは、先頭がA、末尾がCで、その間にちょうど1文字が入る3文字の文字列だけに一致します。ACは間が0文字、ABBCは間が2文字、ABCDは末尾がCではないため、いずれも一致しません。なお、A%Cというパターンであれば、ACやABBCにも一致します。
Q4 : WHERE price = NULL という条件をつけてSELECTを実行すると、結果はどうなりますか?
正解は、1行も返らない、です。SQLではNULLは不明な値を表し、NULLとの比較(=やなど)の結果は真でも偽でもなくUNKNOWNとなります。WHERE句はUNKNOWNの行を結果に含めないため、price = NULLでは該当する行が1件も得られません。NULLかどうかを判定するには、必ずIS NULLまたはIS NOT NULLを使います。この仕様はNULLの取り扱いでよくある落とし穴です。
Q5 : グループ内で同じ値があっても同順位にせず、1, 2, 3 と重複しない連番を振りたい場合に使うウィンドウ関数はどれですか?
正解はROW_NUMBER()です。ROW_NUMBER()は、ORDER BYで指定した並び順に従って、同じ値があっても必ず1, 2, 3と重複しない連番を割り当てます。RANK()は同順位の場合に同じ順位を付け、その次の順位を飛ばします(1, 1, 3など)。DENSE_RANK()は同順位に同じ順位を付けますが順位を飛ばしません(1, 1, 2など)。NTILE(n)は行をn個のグループに分割する関数で、連番を振る用途ではありません。
Q6 : 重複する行を排除せず、2つのSELECT結果をそのまま結合するために使う演算子はどれですか?
正解はUNION ALLです。UNIONは2つの結果を結合したうえで重複行を取り除くため、内部で重複判定の処理が必要になります。これに対してUNION ALLは重複を排除せずにすべての行をそのまま結合するので、一般的にUNIONより高速です。INTERSECTは両方の結果に存在する行だけを返す共通部分、EXCEPT(DBによってはMINUS)は一方にのみ存在する行を返す差集合を求める演算子であり、結合の目的とは異なります。
Q7 : customersテーブルとordersテーブルをLEFT JOINしたとき、注文が1件もない顧客の行はどのように扱われますか?
正解は、orders側の列がNULLとなって結果に含まれる、です。LEFT JOIN(LEFT OUTER JOIN)は左側のテーブルの行をすべて残す結合方法で、右側に対応する行が存在しない場合は、右側テーブルの列がすべてNULLで埋められます。注文のない顧客を除外するのはINNER JOINの挙動です。この性質を利用して、WHERE orders.id IS NULLと組み合わせれば、注文履歴のない顧客だけを抽出することもできます。
Q8 : 5行あるテーブルの列Xのうち、2行がNULLで3行に値が入っています。SELECT COUNT(X) FROM テーブル の結果はどれですか?
正解は3です。COUNT(*)は行数そのものを数えるためNULLを含む5行がカウントされますが、COUNT(列名)は指定した列の値がNULLでない行だけを数えます。この例では値が入っている3行のみが対象となり、結果は3になります。なお、COUNT(DISTINCT 列名)とすれば、NULLを除いた重複のない値の個数を数えることができます。NULLの扱いは集計関数の結果に影響するため、実務でも注意が必要です。
Q9 : SELECT文の論理的な処理順序として、正しいものはどれですか?
正解はFROM → WHERE → SELECTの順です。SQLは書く順番と論理的な処理順序が異なり、まずFROMでどのテーブルを対象にするかを決め、次にWHEREで行を絞り込み、その後GROUP BY、HAVINGを経て、最後にSELECTで出力する列が決まり、ORDER BYで並べ替えられます。この順序のため、多くのDBではWHERE句の中でSELECT句で付けた列の別名を使うことができません。この仕組みを理解しておくと、エラーの原因を推測しやすくなります。
Q10 : GROUP BYでグループ化した後の集計結果に対して、条件による絞り込みを行うSQLの句はどれですか?
正解はHAVINGです。WHERE句はグループ化する前の個々の行を絞り込む句であり、SUMやCOUNTといった集計関数を条件に使うことができません。一方、HAVING句はGROUP BYでグループ化した後の結果に対して条件を指定でき、たとえばHAVING COUNT(*) >= 5と書けば、5件以上あるグループだけを取り出せます。ORDER BYは結果の並べ替え、LIMITは取得する行数の制限を行う句で、絞り込みとは役割が異なります。
まとめ
いかがでしたか? 今回はSQLクイズをお送りしました。
皆さんは何問正解できましたか?
今回はSQLクイズを出題しました。
ぜひ、ほかのクイズにも挑戦してみてください!
次回のクイズもお楽しみに。