CURRICULUM  /  LAYER 01  /  CHAPTER 06

データベースとSQLを読む

ウェブサービスを支えているのは、画面でもコードでもなく、データです。

そのデータはどこに住んでいるのか。

どう呼び出されるのか。

ここが見えると、ウェブ全体の仕組みが一段立体的になります。

データはどこにあるのか

あなたがTwitterで投稿したツイート、Amazonで買った商品の履歴、LINEで送ったメッセージ。

これらはすべて、サーバーのどこかに保管されています。

PHPが動いているサーバーの「隣の部屋」のようなところに、専用のスペースがあって、そこに行儀よく並んでいるのです。

その保管場所をデータベース(Database、略してDB)と呼びます。

もう少し正確に言うと、データを整理して保管し、必要なときに素早く取り出すための専用ソフトウェアのことです。

代表的なものに MySQL、PostgreSQL、SQLite などがあり、このサイトや世の中の多くのウェブサービスで使われています。

— Key Concept

ウェブサービスの全体像は、こう整理できます。

HTML/CSS/JS がブラウザで見た目と動きを作り、 PHP がサーバーで処理を行い、 データベース がそのデータを保管する。

この3者の連携で、あらゆるウェブサービスは成り立っています。

データベースは「巨大なスプレッドシート」だと思ってOK

データベースというと何やら難しそうに聞こえますが、入門段階ではシンプルに考えてOKです。

「たくさんの行と列からなる、Excelのような表がいくつも並んでいるもの」

この理解で十分スタートできます。

たとえば、このCodeReaderというサイトが将来、学習者アカウントを持つようになったとしましょう。

そのとき、データベースの中にはこんな表(テーブルと呼びます)が作られるはずです。

users テーブル
+----+-----------+-------------------+---------------------+
| id | name      | email             | created_at          |
+----+-----------+-------------------+---------------------+
|  1 | 山田太郎  | yamada@example.jp | 2026-04-01 10:23:00 |
|  2 | 田中花子  | tanaka@example.jp | 2026-04-02 14:51:00 |
|  3 | 佐藤一郎  | sato@example.jp   | 2026-04-05 09:12:00 |
+----+-----------+-------------------+---------------------+

横の並び(1行)が1人のユーザーを表し、縦の並び(1列)がそのユーザーのある属性(id、名前、メールアドレス…)を表します。

縦の列のことをカラム(column)、横の行のことをレコード(record)や行(row)と呼びます。

テーブルは複数あってよく、それぞれが別の種類のデータを保管します。

usersテーブルの他に、posts(投稿)テーブル、comments(コメント)テーブル、などがあって、それらが互いに関係を持ちながらデータ全体を構成する。

これがリレーショナルデータベースという考え方です。

SQL ─ データベースとの会話の言葉

では、この表から「山田太郎さんのメールアドレスを取り出したい」と思ったとき、どうすればいいでしょうか。

ここで登場するのが、データベース専用の言葉、SQL(エスキューエル、または「シークェル」と読みます)です。

SQLの面白いところは、ほとんど英語の文章のように読めることです。

さっきの問いへの答えは、こう書きます。

SELECT email FROM users WHERE name = '山田太郎';

読み下すとこうです。

「emailを選んで(SELECT email)、usersテーブルから(FROM users)、nameが山田太郎である条件で(WHERE name = '山田太郎')」。

ほぼ英文のまま意味が通じますね。

— Key Concept

SQLは「人間が読みやすいように」意図的に英語っぽく設計された言語です。

だからこそ、セレクタを覚えるより、プロパティを覚えるより、ずっと素直に読めます。

深く理解するのは後回しで、まずは「読める」状態を目指しましょう。

SQLの基本4動詞 ─ SELECT, INSERT, UPDATE, DELETE

SQLには100以上の命令がありますが、実務で頻繁に使うのは4つだけです。

この4つは「CRUD」(クラッド)と総称されます。

Create(作る)・Read(読む)・Update(更新する)・Delete(削除する)の頭文字です。

1. SELECT ─ データを読み取る

一番よく使う命令です。

条件に合うデータを取り出します。

-- usersテーブルからすべての行を取る
SELECT * FROM users;

-- nameとemailの列だけを取る
SELECT name, email FROM users;

-- idが3のユーザーを取る
SELECT * FROM users WHERE id = 3;

-- 新しく登録された順に5件取る
SELECT * FROM users ORDER BY created_at DESC LIMIT 5;

主なキーワードを整理しておきます。

2. INSERT ─ 新しいデータを追加する

-- 新しいユーザーを追加する
INSERT INTO users (name, email)
VALUES ('鈴木健太', 'suzuki@example.jp');

「usersテーブルに、nameとemailの列へ値を入れる」という読み方です。

idとcreated_atは指定していませんが、これらはデータベース側で自動的に付けてくれるよう設定されていることが多いです(idは自動連番、created_atは現在時刻)。

3. UPDATE ─ 既存のデータを書き換える

-- idが2のユーザーのメールアドレスを変更する
UPDATE users
SET email = 'new_address@example.jp'
WHERE id = 2;
— 恐ろしい落とし穴

UPDATE文で WHERE を書き忘れると、テーブルの全行が書き換わります

「テーブルの全ユーザーのメールアドレスが、たった1つの値に上書きされる」という惨事が起こり得ます。

UPDATEを書くときは、必ず WHERE を先に書いてから、本体を書く習慣を付けましょう。

4. DELETE ─ データを削除する

-- idが3のユーザーを削除する
DELETE FROM users WHERE id = 3;
— もっと恐ろしい落とし穴

DELETE文も、WHERE を忘れるとテーブルのすべての行が消えます

UPDATE以上に取り返しがつかない事故になりえます。

実務では、いきなりDELETEを打つ前に、同じ条件でまずSELECTして「消す対象が本当に合っているか」を確認するのが鉄則です。

PHPからSQLを使う ─ ただし、危険な書き方がある

ウェブサイトでは、人間が手でSQLを打つのではなく、PHPがSQLを組み立ててデータベースに送ります。

たとえば、ログインフォームから送られてきたユーザー名でデータベースを検索する処理は、こんなふうに書きたくなるかもしれません。

<?php
// ⚠️ これは危険な書き方の例です。真似しないでください
$username = $_POST['username'];

$sql = "SELECT * FROM users WHERE name = '" . $username . "'";

$result = $db->query($sql);
?>

一見、何の問題もなさそうに見えます。

動きます。

普通のユーザー名なら、ちゃんと検索できます。

でも、このコードにはウェブの歴史で最も有名な脆弱性が潜んでいます。

それがSQLインジェクション(SQL Injection)です。

もし悪意のあるユーザーが、ユーザー名の欄に次のような文字列を入力したらどうなるでしょうか。

' OR '1'='1

すると、PHPが組み立てるSQL文は、こうなります。

SELECT * FROM users WHERE name = '' OR '1'='1'

読み下してみてください。

「nameが空である、または、'1'が'1'に等しい条件で」。

'1'は必ず'1'に等しいので、この条件は常に真になります。

結果として、全ユーザーの情報がごっそり返ってきます

もっと悪質な攻撃では、ユーザー入力に「'; DROP TABLE users; --」のような文字列を混ぜることで、テーブルをまるごと削除することすらできます。

これが、20年以上もウェブ開発者を悩ませ続けている、SQLインジェクションという攻撃の正体です。

— 覚えておいてください

SQLインジェクションは、ユーザー入力をSQL文に文字列として連結するという、たった一つの習慣から生まれます。

これを防ぐには「プリペアドステートメント」という仕組みを使う必要がありますが、詳しくはLayer 02のSQLインジェクション章で徹底的にやります。

今のうちに心に刻んでおいてほしいのは、$変数 を直接SQL文に連結するコードを見たら、脆弱性を疑え」ということだけです。

正しい書き方(予告編)

Layer 02で詳しくやりますが、正しく書くとこうなります。

今はじっくり読まなくても大丈夫です。

「こういう書き方があるらしい」とだけ覚えておいてください。

<?php
// ✓ 安全な書き方(プリペアドステートメント)
$username = $_POST['username'];

$stmt = $db->prepare("SELECT * FROM users WHERE name = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
?>

ポイントは、SQLの中に変数を直接書いていないこと。

代わりに ? という「プレースホルダ」を置いて、後から値を渡しています。

こうすると、データベースが「SQL本体」と「ユーザーが渡した値」をきっちり区別してくれるため、値の中に悪意のあるSQL文が混じっていても、それはあくまで「文字列」として扱われ、SQL文として実行されることはありません。

解読演習

— EXERCISE 01-06-A

このSQLは何をするか、日本語で説明してください

次のSQLを読んで、それぞれ何をしているか、自分の言葉で説明してみてください。

英語っぽく読めば意味は取れるはずです。

-- ①
SELECT name FROM users WHERE id = 5;

-- ②
SELECT * FROM posts ORDER BY created_at DESC LIMIT 10;

-- ③
UPDATE users SET email = 'new@example.jp' WHERE id = 1;

-- ④
INSERT INTO comments (post_id, body) VALUES (42, '面白い記事でした');
解答例を見る

usersテーブルから、idが5であるユーザーのname(名前)を取得する。

1人だけが返ってくるはずです(idは重複しない前提なので)。

postsテーブルから、created_atの降順(新しい順)で、最初の10件をすべての列で取得する。

「最新の投稿10件」を取るときの定番パターンです。

usersテーブルの、idが1のユーザーのemail列を 'new@example.jp' に書き換える。

WHEREで1人に絞っているので安全ですが、もしWHEREを書き忘れていたら全ユーザーが書き換わる恐ろしいSQLになります。

commentsテーブルに新しい行を追加する。

post_id列には42、body列には '面白い記事でした' を入れる。

つまり、id=42の投稿に対する新しいコメントを登録するSQLです。

— EXERCISE 01-06-B

このSQLは、なぜ危険か

AIが書いてきたログイン処理のPHPコードです。

このコードには、本章で説明した脆弱性があります。

どこがどう危険か、また、悪意のあるユーザーがどんな文字列を入力すると何が起きるか、説明してみてください。

<?php
$user = $_POST['username'];
$pass = $_POST['password'];

$sql = "SELECT * FROM users WHERE name = '" . $user
     . "' AND password = '" . $pass . "'";

$result = $db->query($sql);
if ($result->num_rows > 0) {
  // ログイン成功とみなす
}
?>
解答例を見る

このコードには、典型的なSQLインジェクション脆弱性があります。

ユーザー入力を文字列として直接SQL文に連結しているからです。

たとえば、ユーザー名の欄に admin' -- と入力したとします(-- はSQLのコメント開始記号)。

すると組み立てられるSQL文は、こうなります:

SELECT * FROM users WHERE name = 'admin' -- ' AND password = ''

-- 以降は全部コメント扱いになるので、password のチェックが丸ごと無効化されます。

パスワードを知らなくても、adminとしてログインできてしまうわけです。

これは実際に過去多くのサイトで起きた事故です。

さらに悪質な攻撃なら、' OR 1=1 -- と入力することで「すべての行が返る」状態を作り、条件なしで「ログイン成功」と判定させることもできます。

他にも、このコードには「パスワードを平文で保存・比較している」という、もう一つの重大な問題もあります(パスワードは必ずハッシュ化して保存するのが鉄則です)。

これはLayer 02の「パスワードと認証」の章で扱います。

SQLインジェクションを防ぐには、プリペアドステートメントを使います。

今は「ユーザー入力と.で連結してSQLを作るコードを見たら即警戒」という眼だけ養えればOKです。

— EXERCISE 01-06-C

このテーブル設計、どこが微妙?

次はSQLではなく、テーブル設計の話です。

ある人が「ブログ記事とコメントを保存するテーブル」を設計しました。

一見動きそうですが、よく考えると不便な点があります。

どこが微妙でしょうか?

posts テーブル
+----+----------+----------+---------------------------+
| id | title    | body     | comments                  |
+----+----------+----------+---------------------------+
|  1 | 初投稿   | 本文...  | 山田:いいね / 田中:最高!  |
|  2 | 2本目    | 本文...  | 佐藤:続き楽しみ           |
|  3 | 3本目    | 本文...  |                           |
+----+----------+----------+---------------------------+
解答例を見る

この設計の問題点は、コメントを1つの文字列として1つのセルに詰め込んでいることです。

一見コンパクトに見えますが、実用上いろいろと困ります。

(1) 特定のコメントだけを取り出せない ── 「山田さんのコメントだけ見たい」と思っても、文字列をプログラムで分解しないと取れません。

SQLの強みが活かせなくなります。

(2) コメントの追加・削除が困難 ── 新しいコメントを追加するたびに、元の文字列を取ってきて、後ろに継ぎ足して、書き戻す、という面倒な処理が必要です。

DELETEはもっと面倒です。

(3) コメントごとの情報(投稿日時、投稿者ID、いいね数など)を持てない ── 1つのセルに文字列で入っているだけなので、各コメントに詳細な属性を持たせられません。

正しい設計は、コメントは別テーブルに分けることです。

こんなふうに:

posts テーブル             comments テーブル
+----+---------+           +----+---------+--------+----------+
| id | title   |           | id | post_id | author | body     |
+----+---------+           +----+---------+--------+----------+
|  1 | 初投稿  |           |  1 |       1 | 山田   | いいね   |
|  2 | 2本目   |           |  2 |       1 | 田中   | 最高!    |
|  3 | 3本目   |           |  3 |       2 | 佐藤   | 続き…    |
+----+---------+           +----+---------+--------+----------+

post_id という列がcommentsテーブルにあり、「このコメントは、postsテーブルのどの行に属するか」を示しています。

これがリレーショナル(関係のある)データベースの本質です。

この設計なら、「id=1の投稿についているコメントをすべて取る」は SELECT * FROM comments WHERE post_id = 1; の一行で済みますし、コメント1つずつに日時や投稿者情報を自由に持たせられます。

テーブル設計は、初心者には難しく感じますが、「関連する情報はテーブルを分けて、IDで繋ぐ」という基本を押さえておくだけで、多くのケースに対応できます。

本格的な設計論はLayer 03で扱います。

この章のまとめ

Layer 01 を終えて ─ あなたはもう「読める」人です

ここまでで、Layer 01「読める」の6章をすべて読み終えました。

おつかれさまでした。

最初の章でブラウザとサーバーの会話の話から始めて、HTML(意味のラベル)、CSS(見た目の装飾)、JavaScript(ブラウザでの動き)、PHP(サーバーでの処理)、そしてデータベースとSQL(データの保管と呼び出し)まで、ウェブを構成する主要な登場人物を一通り眺めてきました。

大事なのは、すべてを完璧に覚えることではありません。

AIが書いてきたコードや、他人のコードを渡されたときに、「このコードはざっくり何をしているか」を自分の言葉で説明できる状態。

それがLayer 01のゴールです。

そこまで来れば、あなたはもう「読める」人です。

次のLayer 02では、いよいよ「気付ける」の訓練に入ります。

読めるだけでは足りません。

そのコードが安全かどうか罠が仕掛けられていないかを見抜けるようになること。

この章で何度か予告した「SQLインジェクション」「XSS」「認証の不備」といったテーマを、一つずつ丁寧に扱っていきます。

ここから先が、AI時代のエンジニアにとって最も重要なスキルセットです。