第2部 SQLを使いこなす
第3章 SQL入門① — SELECTで読む
いよいよSQLを本格的に書き始めます。主役はデータを読み出すSELECT文。列を選び、条件で絞り、並べ替える——実務のSQLの8割はこの章の技術でできています。手を動かしながら進めましょう。
🎯 この章で学ぶこと
- SQLが「宣言型」の言語であること、DDL・DML・DCLという3分類
- このコース共通のサンプルDB(company.db)を自分の手で作る
- SELECTの基本 — 列の指定、*、AS(別名)、DISTINCT、計算列
- WHEREによる絞り込み — 比較演算子、AND/OR/NOT、LIKE、IN、BETWEEN、IS NULL
- ORDER BYによる並べ替えと、LIMIT/OFFSETによる件数制限
3.1 SQLという言語の性格
SQLはPythonのようなプログラミング言語とは性格が根本的に違います。Pythonでは「ファイルを開いて、1行ずつ読んで、条件に合ったら配列に足して……」と手順を書きますが、SQLでは「何が欲しいか」だけを書きます。「社員テーブルから、給与が35万以上の人の名前が欲しい」と宣言すれば、どうやって探すかはDBMSが考えてくれる——この性格を宣言型と呼びます。レストランで「カレーください」と注文するのがSQL、厨房に入って自分で調理するのが手続き型言語、というイメージです。
SQLの命令は、役割ごとに3つに分類されます。
| 分類 | 正式名称 | 役割 | 代表的な命令 |
|---|---|---|---|
| DDL | Data Definition Language(定義) | テーブルなど「入れ物」を作る・変える・消す | CREATE、ALTER、DROP |
| DML | Data Manipulation Language(操作) | データを読む・入れる・変える・消す | SELECT、INSERT、UPDATE、DELETE |
| DCL | Data Control Language(制御) | 権限を与える・取り上げる | GRANT、REVOKE |
第2章のCREATE TABLEはDDLでした。この章と第4章はDMLの王様であるSELECTを、第5章で残りのDML(INSERT・UPDATE・DELETE)を扱います。DCLは権限管理の話なので、セキュリティを扱う第9章で登場します。
3.2 サンプルDBの準備 — company.db
この章から第5章まで、小さな会社のデータベース company.db を共通で使います。部署テーブルと社員テーブルの2つだけの、しかし実務の縮図のようなDBです。まず、次のSQLを見てください。
CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
CREATE TABLE employees (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
dept_id INTEGER REFERENCES departments(id),
hire_date TEXT,
salary INTEGER
);
INSERT INTO departments VALUES (10,'総務部'),(20,'営業部'),(30,'情報システム部');
INSERT INTO employees VALUES
(1,'佐藤 花子',10,'2019-04-01',320000),
(2,'鈴木 一郎',20,'2021-10-01',350000),
(3,'田中 実',10,'2023-04-01',280000),
(4,'高橋 美咲',30,'2020-04-01',400000),
(5,'伊藤 健',20,'2018-04-01',380000),
(6,'渡辺 由紀',NULL,'2024-04-01',300000);
第2章で学んだ要素が全部入っています。主キー(id)、外部キー(dept_id)、NOT NULL制約、そして渡辺さんの dept_id はNULL=配属未定です。このNULLの1行が、あとでとても良い教材になります。ターミナルで sqlite3 company.db を起動し、上のSQLを貼り付けて実行してください。あわせて、結果を見やすくする2つのドットコマンドも実行しておきます。
$ sqlite3 company.db
SQLite version 3.45.1
Enter ".help" for usage hints.
sqlite> .mode column
sqlite> .headers on
sqlite> (ここに上のSQLを貼り付けて実行)
sqlite> .tables
departments employees
sqlite> SELECT * FROM employees;
id name dept_id hire_date salary
-- --------- ------- ---------- ------
1 佐藤 花子 10 2019-04-01 320000
2 鈴木 一郎 20 2021-10-01 350000
3 田中 実 10 2023-04-01 280000
4 高橋 美咲 30 2020-04-01 400000
5 伊藤 健 20 2018-04-01 380000
6 渡辺 由紀 2024-04-01 300000
上のSQLを setup.sql というファイルに保存しておけば、DBを壊しても sqlite3 company.db < setup.sql で何度でも作り直せます(作り直す前に古い company.db は削除してください)。「壊しても平気」という安心感は学習速度を上げます。遠慮なく実験してください。
3.3 SELECTの基本
SELECT文の最小形は「どの列を(SELECT)、どのテーブルから(FROM)」の2部品です。
SELECT name, salary FROM employees;
sqlite> SELECT name, salary FROM employees;
name salary
--------- ------
佐藤 花子 320000
鈴木 一郎 350000
田中 実 280000
高橋 美咲 400000
伊藤 健 380000
渡辺 由紀 300000
全列が欲しいときは SELECT * FROM employees; と書きます。* は「すべての列」の意味ですが、実務のプログラムに埋め込むSQLでは「必要な列だけ書く」のが行儀とされます。列が増えたときに転送量が無駄に増え、どの列に依存しているかも分からなくなるからです。
AS — 列に別名を付ける
計算式や英語の列名は、AS で読みやすい別名(エイリアス)に変えられます。計算列と組み合わせると効果的です。月給から年収を計算してみましょう。
sqlite> SELECT name AS 氏名, salary * 12 AS 年収 FROM employees;
氏名 年収
--------- -------
佐藤 花子 3840000
鈴木 一郎 4200000
田中 実 3360000
高橋 美咲 4800000
伊藤 健 4560000
渡辺 由紀 3600000
データベースに「年収」列は存在しません。保存するのは元データ(月給)だけ、導き出せる値はSELECTのたびに計算する——これがRDBの流儀です。年収列を持ってしまうと、月給を更新したのに年収を更新し忘れる、という不整合の温床になります。
DISTINCT — 重複を除いて一覧する
「社員がいる部署のIDを知りたい」とき、普通にSELECTすると同じ値が何度も出ます。DISTINCT を付けると重複が除かれます。
sqlite> SELECT DISTINCT dept_id FROM employees;
dept_id
-------
10
20
30
よく見ると、最後に空の行があります。渡辺さんのNULLも「1つの値」として数えられているのです。NULLはこうして予想外の場所に顔を出します。
3.4 WHEREで絞り込む
SELECTの真価は WHERE 句にあります。「どの行を」の条件をここに書きます。まず比較演算子から。
| 演算子 | 意味 | 例 |
|---|---|---|
= | 等しい | dept_id = 20 |
<> | 等しくない(!= も可) | dept_id <> 10 |
< / > | より小さい / より大きい | salary > 350000 |
<= / >= | 以下 / 以上 | salary >= 350000 |
sqlite> SELECT name, salary FROM employees WHERE salary >= 350000;
name salary
--------- ------
鈴木 一郎 350000
高橋 美咲 400000
伊藤 健 380000
文字列の比較は WHERE name = '田中 実' のようにシングルクォートで囲みます。日付をTEXTで持っている私たちのDBでは、hire_date >= '2020-01-01' のような文字列比較がそのまま日付の前後関係になります(YYYY-MM-DD形式は辞書順=時系列順になるのがミソです)。
AND・OR・NOT — 条件を組み合わせる
sqlite> SELECT name, dept_id, salary FROM employees
...> WHERE dept_id = 20 AND salary > 360000;
name dept_id salary
------- ------- ------
伊藤 健 20 380000
ANDはORより優先されるため、WHERE dept_id = 10 OR dept_id = 20 AND salary > 360000 は「総務部全員と、営業部の36万超」という意図しない意味になります。「総務部か営業部で、かつ36万超」なら WHERE (dept_id = 10 OR dept_id = 20) AND salary > 360000 とカッコで明示してください。カッコは読み手への思いやりでもあります。
LIKE — パターンで探す
「名前が『佐』で始まる人」のようなあいまい検索には LIKE を使います。% が「0文字以上の任意の文字列」、_ が「任意の1文字」です。
sqlite> SELECT name FROM employees WHERE name LIKE '佐%';
name
---------
佐藤 花子
sqlite> SELECT name, hire_date FROM employees WHERE hire_date LIKE '2023%';
name hire_date
------- ----------
田中 実 2023-04-01
IN と BETWEEN — 読みやすい範囲指定
IN は「この中のどれか」、BETWEEN は「AからBの範囲(両端を含む)」です。ORを並べるより意図が明確になります。
sqlite> SELECT name, dept_id FROM employees WHERE dept_id IN (10, 30);
name dept_id
--------- -------
佐藤 花子 10
田中 実 10
高橋 美咲 30
sqlite> SELECT name, salary FROM employees WHERE salary BETWEEN 300000 AND 360000;
name salary
--------- ------
佐藤 花子 320000
鈴木 一郎 350000
渡辺 由紀 300000
IS NULL —「= NULL」は永遠にヒットしない
配属未定の渡辺さんを探してみます。直感的には = NULL と書きたくなりますが——
sqlite> SELECT name FROM employees WHERE dept_id = NULL;
sqlite>
1行も返りません。NULLは「値がない」状態なので、NULLと何かを比較した結果は真にも偽にもならない(不明になる)からです。NULL同士の比較 NULL = NULL ですら真になりません。NULLの判定には専用の書き方 IS NULL / IS NOT NULL を使います。
sqlite> SELECT name FROM employees WHERE dept_id IS NULL;
name
---------
渡辺 由紀
sqlite> SELECT COUNT(*) AS 配属済み FROM employees WHERE dept_id IS NOT NULL;
配属済み
--------
5
「未配属の社員一覧」を = NULL で書くと、エラーにはならず静かに0件が返ります。エラーが出ないぶん気づきにくく、「未配属者はいません」という誤った報告につながります。WHERE句を書くとき、その列はNULLになりうるか?を確認する癖をつけてください。
3.5 並べ替えと件数制限
結果の並び順は ORDER BY で指定します。ASC(昇順・省略時の既定)か DESC(降順)を付けます。
sqlite> SELECT name, salary FROM employees ORDER BY salary DESC;
name salary
--------- ------
高橋 美咲 400000
伊藤 健 380000
鈴木 一郎 350000
佐藤 花子 320000
渡辺 由紀 300000
田中 実 280000
キーはカンマで複数指定できます。「部署順、同じ部署内では給与の高い順」ならこうです。
SELECT name, dept_id, salary FROM employees
ORDER BY dept_id ASC, salary DESC;
ORDER BYを書かないとき、結果の並びは保証されません。今日はID順に見えても、データ量やDBMSの都合で明日は変わりえます。「並び順に意味があるなら必ずORDER BYを書く」——これはどのDBMSでも通用する鉄則です。
件数を制限するのが LIMIT、先頭を読み飛ばすのが OFFSET です。「給与トップ3」はこう書けます。
sqlite> SELECT name, salary FROM employees ORDER BY salary DESC LIMIT 3;
name salary
--------- ------
高橋 美咲 400000
伊藤 健 380000
鈴木 一郎 350000
sqlite> SELECT name, salary FROM employees ORDER BY salary DESC LIMIT 3 OFFSET 3;
name salary
--------- ------
佐藤 花子 320000
渡辺 由紀 300000
田中 実 280000
LIMIT 3 OFFSET 3 は「4位から3人」。Webサイトの「次のページ」ボタンの裏側は、多くの場合この仕組み(ページネーション)です。
「SELECTは読むだけだから安全」と思っていませんか。顧客テーブルを全件SELECTしてCSVに保存し、USBメモリで持ち出せば——それは立派な情報漏えいです。実際、内部不正による漏えい事件の多くは、正規の権限でのSELECTから始まっています。だから実務では、①業務アプリ用のDBユーザーには読み取り専用(SELECTのみ)の権限を与え、書き込みが必要な処理と分離する、②抽出は「必要な列・必要な行だけ」を原則にする(全件・全列の抽出には申請を要する運用にする)、③誰がいつどんなSELECTを実行したかのログを残す、といった対策を組み合わせます。権限の設定(GRANT)は第9章で学びますが、「読むだけの操作にもリスクと責任がある」という感覚は今日から持ってください。
まとめ
- SQLは「何が欲しいか」を書く宣言型言語。命令はDDL(定義)・DML(操作)・DCL(制御)に分類され、SELECTはDMLの中心
- SELECTは「どの列を・どのテーブルから」。ASで別名、DISTINCTで重複除去、salary * 12 のような計算列も書ける
- WHEREで行を絞る。比較演算子・AND/OR/NOT(カッコで優先順位を明示)・LIKE(%と_)・IN・BETWEENを使い分ける
- NULLの判定は IS NULL / IS NOT NULL。「= NULL」はエラーにならず静かに0件を返す罠
- 並び順に意味があるなら必ずORDER BY。LIMIT/OFFSETで件数制限とページネーション
- SELECTだけでも情報漏えいは起きる。読み取り専用権限と「必要最小限の抽出」が運用の基本
練習問題
いずれも company.db に対して実際にSQLを実行し、結果を確かめてください。
営業部(dept_id が 20)の社員のうち、給与が30万円以上の人の「名前と給与」を表示してください。
解答を見る
SELECT name, salary
FROM employees
WHERE dept_id = 20 AND salary >= 300000;
実行結果:
name salary
--------- ------
鈴木 一郎 350000
伊藤 健 380000
2つの条件を両方満たす必要があるのでANDでつなぎます。営業部の2人はどちらも30万以上なので、結果的に営業部全員が表示されますが、条件としては別物です。
2020年1月1日以降に入社した社員の「名前と入社日」を、入社日の早い順(昇順)に表示してください。
解答を見る
SELECT name, hire_date
FROM employees
WHERE hire_date >= '2020-01-01'
ORDER BY hire_date ASC;
実行結果:
name hire_date
--------- ----------
高橋 美咲 2020-04-01
鈴木 一郎 2021-10-01
田中 実 2023-04-01
渡辺 由紀 2024-04-01
日付はTEXT型ですが、YYYY-MM-DD形式なら文字列の大小比較がそのまま日付の前後になります。ASC は省略しても同じ結果ですが、「並び順に意図がある」ことを示すために書く習慣をおすすめします。
配属先が未定(dept_id がNULL)の社員の名前を表示してください。また、WHERE dept_id = NULL と書くと何が起きるかを実際に試し、なぜそうなるのかを説明してください。
解答を見る
正しいSQLと結果:
sqlite> SELECT name FROM employees WHERE dept_id IS NULL;
name
---------
渡辺 由紀
一方 WHERE dept_id = NULL は、エラーにならず0件を返します。NULLは「値がない」状態であり、NULLを含む比較の結果は真にも偽にもならない「不明」になるため、どの行もWHEREを通過できないからです。NULLの判定には必ず IS NULL / IS NOT NULL を使います。エラーが出ないぶん発見が遅れる、SQLで最も有名な罠のひとつです。