変更要約: 初版(主題S3・S3.1〜S3.3)
3.1SQL問い合わせ(SELECT・JOIN・副問合せ・集合演算・DML)
データを取り出すSELECT文の各句(WHERE・ORDER BY・GROUP BY・HAVING・DISTINCT・LIMIT・OFFSET)、複数テーブルを結合するJOIN(INNER JOIN・LEFT JOIN・RIGHT JOIN・FULL JOIN・CROSS JOIN)、クエリの中に埋め込む副問合せ、結果集合を演算するUNION・INTERSECT・EXCEPT、データを変更するINSERT・UPDATE・DELETEを学びます。
OSS-DB Silverの出題範囲で最大の重み(S3.1・重13)を持つのがこの領域です。SELECT文は単に「データを取り出す」だけでなく、絞り込み・並べ替え・集約・重複排除の各句を正確な順序と役割で組み合わせて初めて意図通りの結果になります。複数テーブルにまたがるデータを扱う実務ではJOINの種類を取り違えると欠落や重複が生じ、副問合せや集合演算はより複雑な条件を1文で表現する力を与えてくれます。
3.1.1SELECT文の各句
- WHERE=行を絞り込む条件句(
SELECT * FROM orders WHERE amount > 1000)。集計前の行に対して働く。ORDER BY=結果を並べ替える(ORDER BY amount DESC、既定は昇順ASC)。LIMIT・OFFSET=取得件数の上限と読み飛ばし件数を指定(LIMIT 10 OFFSET 20でページング)。 - GROUP BY=指定した列の値ごとに行をグループ化し、集約関数(count/sum等)と組み合わせて使う。HAVING=グループ化後の集約結果に対する絞り込み条件(
GROUP BY customer_id HAVING count(*) > 5)。WHEREは集約前・HAVINGは集約後という順序の違いが最頻出の論点。 - DISTINCT=結果から重複行を除去(
SELECT DISTINCT city FROM customers)。SELECT文の実行順序はおおむね FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT/OFFSET の順であり、記述順とは異なる点に注意。 - NULL(値が無い=「不明」)は三値論理で扱う:
列 = NULLは真でも偽でもなく常に不定(unknown)となり1行も返らない。NULL の判定には必ずIS NULL/IS NOT NULLを使う(WHERE col IS NULL)。集約ではcount(*)は行数を数えるがcount(列)は NULL を除外し、sum/avgも NULL を無視する。GROUP BYは NULL 同士を1グループにまとめる。この「= NULL は使えず IS NULL を使う」は最頻出のひっかけ。
3.1.2JOIN・副問合せ・集合演算・DML
- INNER JOIN=両テーブルで結合条件が一致する行のみ返す(単に
JOINと書けばINNER JOIN)。LEFT JOIN=左側テーブルの全行を残し、右側に一致がなければNULLで埋める。RIGHT JOIN=右側テーブルの全行を残す(LEFT JOINの左右反転)。FULL JOIN=両側の全行を残し、どちらか一方にしか無ければNULLで埋める。CROSS JOIN=結合条件なしに全組み合わせ(直積)を生成。 - 副問合せ(サブクエリ)=SELECT文の中に別のSELECT文を埋め込む構文。
WHERE column IN (SELECT ...)のようにWHERE句内で使う他、FROM句・SELECT句にも書ける。集合演算=UNION(2つの結果を結合し重複除去。重複を残すUNION ALLもある)・INTERSECT(両方に共通する行のみ)・EXCEPT(片方から相手にある行を除いた差集合)。列数と型の互換が前提。 - INSERT=新規行を追加(
INSERT INTO t (col1,col2) VALUES (1,'a'))。UPDATE=既存行を変更(UPDATE t SET col1=2 WHERE id=1、WHEREを忘れると全行が更新される)。DELETE=行を削除(DELETE FROM t WHERE id=1、WHERE省略で全行削除。テーブル構造ごと消すDROP TABLEとは別物)。
「WHEREは集約前・HAVINGは集約後」「LEFT/RIGHT/FULL JOINはNULL埋めで一致しない側も残す・INNER JOINは一致行のみ」「UNIONは重複除去・UNION ALLは重複を残す」「UPDATE/DELETEでWHEREを省略すると全行が対象になる」が最頻出です。SELECT文の実行順序(FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT)を問う設問も定番です。
受注テーブルorders(id, customer_id, amount, order_date)と顧客テーブルcustomers(id, name, city)があるとして、実務でよくある分析クエリの組み立てを追ってみましょう。「東京の顧客ごとの合計注文金額のうち、5000円を超える顧客だけを金額の多い順に見たい」という要求は、まずcustomersとordersをcustomer_idで結合する必要があります。SELECT c.name, sum(o.amount) AS total FROM customers c JOIN orders o ON c.id = o.customer_id WHERE c.city = 'Tokyo' GROUP BY c.name HAVING sum(o.amount) > 5000 ORDER BY total DESC;のように書くと、WHEREで東京の顧客に絞り込んでから集約し、HAVINGで集約結果を絞り込み、最後にORDER BYで並べ替えるという流れが一文に凝縮されます。ここでもし「注文が一度もない顧客も一覧に含めたい」という要求が加わると、INNER JOINでは注文のない顧客が消えてしまうため、LEFT JOIN orders o ON ...に変更し、sum(o.amount)がNULLになる点をcoalesce関数等で補う判断が必要になります。また「過去に一度でも1万円を超える注文をした顧客のIDだけ欲しい」という要求には、SELECT DISTINCT customer_id FROM orders WHERE amount > 10000のような単純な副問合せで十分な場面もあれば、WHERE customer_id IN (SELECT customer_id FROM orders WHERE amount > 10000)のように他のSELECT文の絞り込み条件として埋め込む場面もあります。集合演算は「今月の新規顧客と昨年の休眠顧客の両方に該当する人」のような複数の独立したクエリ結果同士の関係を求めたいときに使い分けます(両方に共通=INTERSECT、片方のみ=EXCEPT)。
| JOIN種別 | 返す行 | 不一致側の扱い |
|---|---|---|
| INNER JOIN | 両側が一致する行のみ | 不一致行は除外 |
| LEFT JOIN | 左テーブルの全行 | 右側不一致はNULL |
| RIGHT JOIN | 右テーブルの全行 | 左側不一致はNULL |
| FULL JOIN | 両テーブルの全行 | 片側のみならNULL |
| CROSS JOIN | 全組み合わせ(直積) | 結合条件そのものが無い |
ひっかけ: 「WHERE句で集計結果を絞り込める」は誤りです。集計後の絞り込みはHAVINGの役割で、WHERE句は集約前の生の行にしか使えず、WHERE句内で集約関数(WHERE sum(amount) > 100等)を直接使うとエラーになります。また「UPDATE文にWHEREを付けなくても対象を明示すれば安全」も誤り=WHEREを省略した時点でテーブルの全行が更新対象になり、DELETEでも同様です。「LEFT JOINとRIGHT JOINは結果が常に同じ」も誤りで、どちらのテーブルを基準に全行残すかが逆になります。
3.1.3この節のまとめ
- WHERE(集約前)→GROUP BY→HAVING(集約後)→ORDER BY→LIMIT/OFFSETという実行順序。DISTINCTで重複除去
- INNER=一致のみ・LEFT/RIGHT/FULL=NULL埋めで一方または両方の全行を保持・CROSS=直積。副問合せはWHERE/FROM/SELECTに埋め込み可
- UNION=重複除去・UNION ALL=重複維持・INTERSECT=共通・EXCEPT=差分。UPDATE/DELETEはWHERE省略で全行対象
進捗の記録にはログインが必要です。
理解度チェック
(軽い確認用)Q1. 顧客ごとの合計注文金額を計算し、その合計が10000円を超える顧客だけを表示したい。この「集計結果に対する絞り込み」に使うべき句はどれか。
Q2. customers テーブルと orders テーブルを結合し、注文が一度も無い顧客も含めて全顧客を一覧表示したい(該当する注文列はNULLでよい)。どのJOINを使うべきか。
Q3. 既存の在庫テーブル products の全行のうち、在庫切れの商品(stock = 0)だけを削除したい。正しい文はどれか。

