🎯 この章で学ぶこと

  • 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つに分類されます。

分類正式名称役割代表的な命令
DDLData Definition Language(定義)テーブルなど「入れ物」を作る・変える・消すCREATE、ALTER、DROP
DMLData Manipulation Language(操作)データを読む・入れる・変える・消すSELECT、INSERT、UPDATE、DELETE
DCLData 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_idNULL=配属未定です。この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を混ぜるときは必ずカッコを

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」は実務事故の定番

「未配属の社員一覧」を = 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なしの並び順は「たまたま」

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は読むだけだから安全」と思っていませんか。顧客テーブルを全件SELECTしてCSVに保存し、USBメモリで持ち出せば——それは立派な情報漏えいです。実際、内部不正による漏えい事件の多くは、正規の権限でのSELECTから始まっています。だから実務では、①業務アプリ用のDBユーザーには読み取り専用(SELECTのみ)の権限を与え、書き込みが必要な処理と分離する、②抽出は「必要な列・必要な行だけ」を原則にする(全件・全列の抽出には申請を要する運用にする)、③誰がいつどんなSELECTを実行したかのログを残す、といった対策を組み合わせます。権限の設定(GRANT)は第9章で学びますが、「読むだけの操作にもリスクと責任がある」という感覚は今日から持ってください。

まとめ

練習問題

いずれも company.db に対して実際にSQLを実行し、結果を確かめてください。

問題 3-1

営業部(dept_id が 20)の社員のうち、給与が30万円以上の人の「名前と給与」を表示してください。

解答を見る
SELECT name, salary
FROM employees
WHERE dept_id = 20 AND salary >= 300000;

実行結果:

name       salary
---------  ------
鈴木 一郎  350000
伊藤 健    380000

2つの条件を両方満たす必要があるのでANDでつなぎます。営業部の2人はどちらも30万以上なので、結果的に営業部全員が表示されますが、条件としては別物です。

問題 3-2

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 は省略しても同じ結果ですが、「並び順に意図がある」ことを示すために書く習慣をおすすめします。

問題 3-3

配属先が未定(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で最も有名な罠のひとつです。