プログラミング言語を整える
中級MySQL を整える。
= Web サービスで広く使われる MySQL の書き方の癖を、同じお題で見比べて学ぶ
MySQL が初めての方は、先に そもそも MySQL とは・どんな場面で使うか をどうぞ。
MySQLとは
Web サービスの裏側で広く使われてきたリレーショナルデータベースで、PHP と組み合わせた LAMP 構成や WordPress でおなじみです。
| 誕生 | 1995年 |
|---|---|
| 作った人・組織 | スウェーデンの MySQL AB(Michael Widenius、David Axmark ら)。Sun Microsystems による買収を経て、現在は Oracle が開発しています |
| 種類 | リレーショナルデータベース(RDB) |
| ライセンス | GPL v2 と商用ライセンスのデュアルライセンス |
| 得意なこと | 読み書きの多い Web アプリケーションのデータを、手軽に導入して扱えることです。 |
| 手元で動かすには | mysql -u root -p |
日本での使われ方
- Web サービスのデータベースとして広く使われています。
- Linux・Apache・MySQL・PHP を組み合わせた LAMP 構成は、レンタルサーバーでもよく提供されている定番の組み合わせです。
- WordPress は MySQL(または互換の MariaDB)を前提に作られているため、ブログや企業サイトの裏側で MySQL が動いていることが多くあります。
- MySQL の元の開発者が始めた派生版(フォーク)の MariaDB もあり、Linux のディストリビューションによっては MySQL の代わりに MariaDB が入っています。
使えるクラウドサービス
- Amazon RDS for MySQL:AWS が提供するマネージドの MySQL で、バックアップやパッチ適用を任せられます。
- Amazon Aurora(MySQL 互換):AWS が独自に作ったストレージの上で動く、MySQL と互換のあるデータベースです。
- Google Cloud SQL for MySQL:Google Cloud が提供するマネージドの MySQL です。
- Azure Database for MySQL:Microsoft Azure が提供するマネージドの MySQL です。
- PlanetScale:MySQL の水平分割の仕組み Vitess を土台にしたデータベースのサービスです。
研修・学習での位置づけ
MySQL は、SQL の入門で PostgreSQL と並んでよく使われるデータベースです。インストールが手軽で、PHP や WordPress と一緒に Web アプリケーションの作り方を学ぶ研修でも題材になります。ほかの DB と比べると、文字の大小を区別しない比較や || の扱いなど独自の振る舞いがあるため、その違いを知っておくと移行のときに迷いません。
関わる資格
- Oracle Certified Professional, MySQL 8.0 Database Administrator
- Oracle Certified Professional, MySQL 8.0 Database Developer
そもそも MySQL とは
Web サイトの裏側で長く使われてきた、定番のリレーショナルデータベース。
MySQL(マイエスキューエル)は、SQL で操作するサーバー型のリレーショナルデータベース(RDB)です。オープンソースとして公開されていて、Web サイトやアプリの保存先として広く使われてきました。Linux・Apache・MySQL・PHP を組み合わせた「LAMP」という構成の名前でも知られています。
PostgreSQL とは同じ SQL を使う仲間で、細かい書き方や機能が違います。MySQL から分かれて作られた互換のデータベースに MariaDB があります。WordPress のようなブログ・サイト作成ソフトも、MySQL か MariaDB に記事を保存しています。SQLite のように組み込むのではなく、サーバーとして動かし、多くの人が同時に使います。
どこで・どんな場面で使うか
-
WordPress などのサイト作成ソフト
記事・ページ・ユーザー情報の保存先として使われています。
-
EC サイトや Web サービス
商品・会員・注文のデータを保存する土台になります。
-
業務システム
社内の管理システムや、PHP・Java などで作られた既存システムの保存先に使われています。
-
クラウドのマネージドサービス
主要なクラウドが、運用を任せられる MySQL 版を用意しています。
-
求人での位置づけ
PHP や Web 系の開発案件、保守案件の募集でよく名前が出ます。
向いている場面・向いていない場面
- 向いている場面:読み取りが多い Web サービス、既存の PHP・LAMP 構成、情報が多い定番の道具を選びたいとき。
- 向いていない場面:手元の大きな CSV を集計するだけの用途(DuckDB など)や、高速なキャッシュ用途(Redis など)。
- 調べやすい。長く使われているので、解説記事や実例が多く、困ったときに調べやすいのも利点です。
このページには練習場がありません(MySQL はブラウザの中で動かせないため)。下のお題の SQL を読み、近い文は SQL の練習場や PostgreSQL のページで試せます。
6つのお題をMySQLで書く
PostgreSQL・MySQL・DuckDB・MongoDB・Redis の5ページで、同じ商品と注文のデータに同じ6つのお題を並べています。SQL の方言の違いや、SQL を使わないデータベースでの考え方の違いがそのまま比べられます。
このページでは SQL を実行できません(MySQL はブラウザの中では動きません)。表示している結果とエラーのメッセージは、MySQL 8.0.46 のサーバーで実際に流して確かめたものです。結果の表は、見比べやすいように全ページで psql に寄せた同じ形にそろえています(NULL は NULL と表示)。mysql コマンドの枠線つきの表示とは見た目が違います。
準備(全お題共通)表を作ってデータを入れるコード。お題2〜6・クイズ・エラーの例は、これを流した後に続けて流した結果です。
CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, category VARCHAR(20) NOT NULL, price INT NOT NULL ); CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, product_id INT NOT NULL, qty INT NOT NULL, ordered_on DATE NOT NULL ); INSERT INTO products (id, name, category, price) VALUES (1, 'りんご', '果物', 150), (2, 'みかん', '果物', 80), (3, '牛乳', '飲み物', 200), (4, 'お茶', '飲み物', 120), (5, '食パン', 'パン', 250); INSERT INTO orders (id, product_id, qty, ordered_on) VALUES (1, 1, 3, '2026-10-01'), (2, 3, 1, '2026-10-01'), (3, 2, 5, '2026-10-02'), (4, 1, 2, '2026-10-03'), (5, 4, 4, '2026-10-03');
SQL(MySQL)
CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, category VARCHAR(20) NOT NULL, price INT NOT NULL ); CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, product_id INT NOT NULL, qty INT NOT NULL, ordered_on DATE NOT NULL ); INSERT INTO products (id, name, category, price) VALUES (1, 'りんご', '果物', 150), (2, 'みかん', '果物', 80), (3, '牛乳', '飲み物', 200), (4, 'お茶', '飲み物', 120), (5, '食パン', 'パン', 250); INSERT INTO orders (id, product_id, qty, ordered_on) VALUES (1, 1, 3, '2026-10-01'), (2, 3, 1, '2026-10-01'), (3, 2, 5, '2026-10-02'), (4, 1, 2, '2026-10-03'), (5, 4, 4, '2026-10-03'); SELECT * FROM products ORDER BY id;
結果
id | name | category | price ----+--------+----------+------- 1 | りんご | 果物 | 150 2 | みかん | 果物 | 80 3 | 牛乳 | 飲み物 | 200 4 | お茶 | 飲み物 | 120 5 | 食パン | パン | 250 (5 行)
AUTO_INCREMENT を付けた列は、値を省くと自動で連番が入ります。ここでは結果を毎回同じにするため id を明示しています。日本語を正しく保存するには、データベースや表の文字コードを utf8mb4 にします(MySQL 8.0 の既定値です)。
同じお題をほかのデータベースで: PostgreSQL・DuckDB・MongoDB・Redis
SQL(MySQL)
SELECT name, category, price
FROM products
WHERE price >= 100
AND category IN ('果物', '飲み物')
ORDER BY price DESC; 結果
name | category | price --------+----------+------- 牛乳 | 飲み物 | 200 りんご | 果物 | 150 お茶 | 飲み物 | 120 (3 行)
MySQL の既定の照合順序(utf8mb4_0900_ai_ci)は大文字と小文字を区別しないため、WHERE name = 'abc' は 'ABC' にも当たります。列名や表名を囲むときは、ダブルクォートではなくバッククォート(`name`)を使います。
同じお題をほかのデータベースで: PostgreSQL・DuckDB・MongoDB・Redis
SQL(MySQL)
SELECT category,
COUNT(*) AS cnt,
ROUND(AVG(price), 1) AS avg_price
FROM products
GROUP BY category
ORDER BY category; 結果
category | cnt | avg_price ----------+-----+----------- パン | 1 | 250.0 果物 | 2 | 115.0 飲み物 | 2 | 160.0 (3 行)
整数の列でも AVG の結果は DECIMAL になり、ROUND を付けないと 115.0000 のように小数4桁で表示されます。MySQL 8.0 は既定で ONLY_FULL_GROUP_BY が有効なので、SELECT に GROUP BY に無い列をそのまま書くとエラーになります。ORDER BY category の並びは照合順序に従うため、カタカナの「パン」が漢字の「果物」より先に来ます。
同じお題をほかのデータベースで: PostgreSQL・DuckDB・MongoDB・Redis
SQL(MySQL)
SELECT o.id AS order_id,
p.name,
o.qty,
p.price * o.qty AS amount
FROM orders AS o
JOIN products AS p ON p.id = o.product_id
ORDER BY o.id; 結果
order_id | name | qty | amount ----------+--------+-----+-------- 1 | りんご | 3 | 450 2 | 牛乳 | 1 | 200 3 | みかん | 5 | 400 4 | りんご | 2 | 300 5 | お茶 | 4 | 480 (5 行)
JOIN は INNER JOIN と同じ意味です。MySQL では || は既定で「または」(OR)の意味になるため、名前と数量を1つの文字列にしたいときは CONCAT(p.name, '×', o.qty) のように CONCAT を使います。
同じお題をほかのデータベースで: PostgreSQL・DuckDB・MongoDB・Redis
SQL(MySQL)
SELECT name, price FROM products ORDER BY price DESC LIMIT 2 OFFSET 2;
結果
name | price --------+------- りんご | 150 お茶 | 120 (2 行)
MySQL では LIMIT 2, 2(「飛ばす件数, 取る件数」の順)とも書けます。数の順番が LIMIT 2 OFFSET 2 と逆に見えるので、読み違えないよう OFFSET を使う書き方がおすすめです。
同じお題をほかのデータベースで: PostgreSQL・DuckDB・MongoDB・Redis
SQL(MySQL)
INSERT INTO products (id, name, category, price) VALUES (2, 'みかん', '果物', 90), (6, 'バナナ', '果物', 100) AS new ON DUPLICATE KEY UPDATE price = new.price; SELECT * FROM products ORDER BY id;
結果
id | name | category | price ----+--------+----------+------- 1 | りんご | 果物 | 150 2 | みかん | 果物 | 90 3 | 牛乳 | 飲み物 | 200 4 | お茶 | 飲み物 | 120 5 | 食パン | パン | 250 6 | バナナ | 果物 | 100 (6 行)
ON DUPLICATE KEY UPDATE は、主キーや UNIQUE の列で重複したときに INSERT の代わりに UPDATE を行います。AS new で入れようとした行に名前を付ける書き方は MySQL 8.0.19 からで、以前の VALUES(price) という書き方は非推奨になりました。
同じお題をほかのデータベースで: PostgreSQL・DuckDB・MongoDB・Redis
出力当てクイズ
準備のデータを入れた後に流したときの、最後の SQL の結果を選んでください。選ぶと正解と理由が出ます(答えはどこにも送られず、記録もしません)。
問1
SELECT 7 / 2;
画面に出るのは?
MySQL の / は整数どうしでも小数の割り算になり、結果は DECIMAL で小数4桁まで表示されます。整数の商がほしいときは 7 DIV 2(結果は 3)を使います。
問2
SELECT 'a' = 'A';
画面に出るのは?
既定の照合順序 utf8mb4_0900_ai_ci の ci は case insensitive(大文字と小文字を区別しない)の意味で、'a' = 'A' は真になります。MySQL では真偽値を 1 と 0 で表示します。
問3
SELECT CONCAT('abc', NULL); 画面に出るのは?
CONCAT は引数に1つでも NULL があると、結果全体が NULL になります。NULL を空文字として扱いたいときは CONCAT_WS や IFNULL(列, '') を使います。
問4
SELECT 'abc' || 'def';
画面に出るのは?
MySQL の || は既定で OR の意味です。'abc' と 'def' は数値に直すと 0 になるため、0 OR 0 で結果は 0 です。文字列をつなぐには CONCAT を使います。
よくあるエラーと直し方
書き始めの人が出会いやすいエラーです。メッセージは英語のまま出るので、どの言葉を手がかりにすればよいかもあわせて載せています。
SQL の書き間違い
エラーになるコード
SELECT * FORM products;
画面に出るメッセージ(抜粋)
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'FORM products' at line 1
何が起きているか:FROM を FORM と書き間違えたため、文の形として読めませんでした。
直し方:near の後ろに示された箇所の少し前を見直します。キーワードの綴りや、カンマ・括弧の過不足がよくある原因です。
表が見つからない
エラーになるコード
SELECT * FROM product;
画面に出るメッセージ(抜粋)
ERROR 1146 (42S02): Table 'shop.product' doesn't exist
何が起きているか:表の名前が products ではなく product になっていました。メッセージには「データベース名.表名」の形で表示されます(ここではデータベース名を shop としています)。
直し方:SHOW TABLES; で表の名前を確かめます。Linux の MySQL では、表名の大文字と小文字が区別される設定が既定です。
主キーの重複
エラーになるコード
INSERT INTO products (id, name, category, price) VALUES (1, 'ぶどう', '果物', 400);
画面に出るメッセージ(抜粋)
ERROR 1062 (23000): Duplicate entry '1' for key 'products.PRIMARY'
何が起きているか:id が 1 の行はすでにあるため、主キーの重複になりました。
直し方:id を省いて AUTO_INCREMENT に任せるか、上書きしたいときは INSERT … ON DUPLICATE KEY UPDATE を使います。
GROUP BY に無い列を選んだ
エラーになるコード
SELECT category, name, COUNT(*) FROM products GROUP BY category;
画面に出るメッセージ(抜粋)
ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'shop.products.name' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
何が起きているか:カテゴリごとにまとめた1行に、name のどれを出すかが決まらないためです。MySQL 8.0 は既定で ONLY_FULL_GROUP_BY が有効です。
直し方:name を GROUP BY に加えるか、MIN(name) や GROUP_CONCAT(name) のように集計関数で包みます。
ほかのデータベースの解説
- PostgreSQL 1986年にカリフォルニア大学バークレー校で始まった研究プロジェクト POSTGRES が元で・リレーショナルデータベース
- DuckDB 2019年・組み込み型の分析用
- MongoDB 2009年・ドキュメント指向データベース
- Redis 2009年・インメモリのキーバリューストア
5つのデータベースと SQLite を並べた比較は データベースを整える にあります。 書いたコードはどこにも送られません(このページに入力欄はクイズの選択だけで、選んだ答えも記録しません)。
このツールについて
Web サービスの裏側で広く使われてきたリレーショナルデータベースで、PHP と組み合わせた LAMP 構成や WordPress でおなじみです。
MySQL の概要と、同じ6つのお題・出力当てクイズ・よくあるエラー(1064 など)。結果は MySQL 8.0 で確かめたもの。
主な機能
- 概要(誕生・作った人・種類・ライセンス)と日本での使われ方・クラウドサービス・研修と資格
- 同じ商品と注文のデータで6つのお題(PostgreSQL・DuckDB・MongoDB・Redis の同じお題へ移れる)
- 出力当てクイズ4問(正解と理由)
- よくあるエラー4件(実際のメッセージ・原因・直し方)
- 結果は MySQL 8.0.46 のサーバーで照合済み・送信ゼロ
よくある質問
Q. このページで MySQL の SQL を実行できますか?
A. できません。MySQL はブラウザの中で動かせないため、解説だけのページにしています。表示している結果とエラーのメッセージは、MySQL 8.0.46 のサーバーで実際に流して確かめたものです。
Q. 'a' = 'A' が 1(真)になるのはなぜですか?
A. MySQL 8.0 の既定の照合順序(utf8mb4_0900_ai_ci)が、大文字と小文字を区別しない比較をするためです。区別したいときは、列や比較に utf8mb4_bin などの照合順序を指定します。
Q. ON DUPLICATE KEY UPDATE の VALUES(price) は使えますか?
A. MySQL 8.0.20 から非推奨になっています。8.0.19 以降は INSERT … AS new … ON DUPLICATE KEY UPDATE price = new.price のように、行に別名を付けて書く形が使えます。
入力値はURLの「#」以降に入るためサーバーには送信されません。リンクを開くと同じ状態を復元します。
続けて整える
データの時点:各ページに記載の版(各プロジェクトの公式リリース)