| この記事の要点 |
|
SELECT 文の基本構文
SELECT は SQL で最も多用される命令で、テーブルから条件にあう行を取り出します。基本形は以下:
SELECT 列1, 列2, ...
FROM テーブル
WHERE 行レベルの条件
GROUP BY グループ化キー
HAVING グループレベルの条件
ORDER BY ソートキー [ASC|DESC]
LIMIT 取得数 OFFSET 開始位置;
これらの句は構文上の順序であり、後述するとおり実際の処理順とは異なります。
論理処理順(とても重要)
| 順序 | 句 | 役割 |
|---|---|---|
| 1 | FROM / JOIN | 元データセットを作る |
| 2 | WHERE | 行レベルで絞る |
| 3 | GROUP BY | グルーピング |
| 4 | HAVING | グループレベルで絞る |
| 5 | SELECT | 列の射影、集約関数評価 |
| 6 | DISTINCT | 重複除外 |
| 7 | ORDER BY | 並べ替え |
| 8 | LIMIT / OFFSET | 件数制限 |
この順序を理解すると「WHERE で COUNT(*) > 5 が使えない」「SELECT のエイリアスを WHERE で使えない(多くの DB で)」といったエラーが腑に落ちます。
WHERE: 行の絞り込み
-- 比較
SELECT * FROM users WHERE age >= 20 AND age < 30;
SELECT * FROM users WHERE age BETWEEN 20 AND 29; -- 同義
-- IN / NOT IN
SELECT * FROM users WHERE country IN ('JP', 'US', 'GB');
-- パターンマッチ
SELECT * FROM users WHERE email LIKE '%@example.com';
SELECT * FROM users WHERE name LIKE 'A%'; -- A で始まる
-- NULL 判定(= NULL は使えない!)
SELECT * FROM users WHERE deleted_at IS NULL;
SELECT * FROM users WHERE deleted_at IS NOT NULL;
-- EXISTS
SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
GROUP BY と HAVING
-- 国別ユーザー数
SELECT country, COUNT(*) AS cnt
FROM users
GROUP BY country
HAVING COUNT(*) >= 100 -- ★ 集約後の絞り込み
ORDER BY cnt DESC;
-- 月別売上集計
SELECT DATE_FORMAT(created_at, '%Y-%m') AS ym,
COUNT(*) AS order_count,
SUM(total) AS total_sum,
AVG(total) AS total_avg
FROM orders
WHERE created_at >= '2026-01-01'
GROUP BY ym
ORDER BY ym;
JOIN
複数テーブルを結合します。代表的な 5 種類:
| JOIN | 意味 | 典型用途 |
|---|---|---|
| INNER JOIN | 両側にマッチする行のみ | 顧客と注文の対応取得 |
| LEFT JOIN | 左側全行 + 右側マッチ行(無ければ NULL) | 注文有無に関わらず全顧客出力 |
| RIGHT JOIN | 右側全行 + 左側マッチ行 | LEFT で書き換え可能(実務では稀) |
| FULL OUTER JOIN | 両側どちらかにある行 | 差分検出 |
| CROSS JOIN | 直積 | 全組合せ生成(カレンダー作成等) |
-- INNER JOIN: 注文を持つ顧客のみ
SELECT u.name, o.total
FROM users u
INNER JOIN orders o ON o.user_id = u.id;
-- LEFT JOIN: 注文有無に関わらず全顧客
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name;
-- 注文の無い顧客を抽出(典型 LEFT JOIN パターン)
SELECT u.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;
-- 自己結合: 上司と部下
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
ORDER BY と LIMIT
-- 単一キー降順
SELECT * FROM users ORDER BY created_at DESC LIMIT 10;
-- 複合キー
SELECT * FROM users ORDER BY country ASC, age DESC;
-- ページング
SELECT * FROM users
ORDER BY id
LIMIT 20 OFFSET 40; -- 3 ページ目(21〜40 件目飛ばして 21 件目から 20 件)
-- NULL の扱い(PostgreSQL)
SELECT * FROM users ORDER BY last_login DESC NULLS LAST;
サブクエリと CTE (WITH)
-- スカラーサブクエリ
SELECT name,
(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count
FROM users u;
-- WITH 句(CTE)で可読性アップ
WITH active_users AS (
SELECT id, name FROM users WHERE last_login > NOW() - INTERVAL 30 DAY
), recent_orders AS (
SELECT user_id, SUM(total) AS total
FROM orders
WHERE created_at > NOW() - INTERVAL 30 DAY
GROUP BY user_id
)
SELECT u.name, COALESCE(o.total, 0) AS recent_total
FROM active_users u
LEFT JOIN recent_orders o ON o.user_id = u.id
ORDER BY recent_total DESC
LIMIT 100;
性能改善: EXPLAIN で実行計画を見る
-- MySQL / PostgreSQL 共通
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';
-- MySQL: 詳細
EXPLAIN FORMAT=JSON SELECT ...;
EXPLAIN ANALYZE SELECT ...; -- MySQL 8.0+, PostgreSQL
-- 確認ポイント:
-- - type が ALL (full scan) になっていないか
-- - rows がテーブルサイズに対して大きすぎないか
-- - key (使用インデックス) が NULL ではないか
-- - Extra に Using filesort / Using temporary が出ていないか
遅い SELECT の典型パターン
| パターン | 原因 | 対処 |
|---|---|---|
| WHERE の列にインデックス無し | full scan | 該当列に INDEX |
WHERE func(col) = ? | 関数適用でインデックス使えず | 関数インデックス or 列側で計算 |
LIKE '%abc%' | 前方一致でないと INDEX 使えない | 全文検索 (FTS) / 別 DB |
| N+1 クエリ | ループ内で SELECT | JOIN や eager loading |
| 大量 OFFSET ページング | OFFSET 値分スキャン | カーソル方式 (id > ?) |
FAQ
Q: SELECT * は使わない方がいい?
A: アプリケーションコードでは必要な列だけ明示するのが推奨。インデックスのみで完結する Covering Index 化や、不要列の転送削減が効きます。ad-hoc クエリでの利用は OK。
Q: なぜ WHERE で集約関数(COUNT 等)が使えない?
A: WHERE は GROUP BY より論理処理順で先に実行されるため、まだ集約値が存在しないからです。集約後の絞り込みは HAVING を使います。
Q: NULL = NULL は true?
A: false(厳密には UNKNOWN)。NULL 判定は IS NULL / IS NOT NULL を使います。SQL の三値論理の代表的な落とし穴です。
子ページ
同階層のページ
- SELECT
- INSERT
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスのソフト一覧|Blender・Metasequoiaと用途別の選び方
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- Visual Studio「デザイナーを表示できません」エラーの原因と対処 2026-10-28 12:06:00
- SSH 認証に失敗しました。再試行してください の対処 2026-10-28 12:06:00
- SSH2 秘密鍵の読み込みに失敗しました の原因と対処 2026-10-28 12:06:00
- TeraTerm のよくあるエラーと対処法完全ガイド — Connection refused / 2026-10-28 12:06:00
- Visual Studio エラー一覧|コンパイルエラー・警告の確認方法と対処 2026-10-28 11:39:57
- Whitelabel Error Page の原因と消し方|Spring Boot のエラーページ 2026-10-28 11:39:57
- Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法 2026-10-28 11:39:57
- SQL WHERE 句完全ガイド 2026-10-28 11:39:57
- SQL JOIN 完全ガイド (INNER/LEFT/RIGHT/FULL OUTER) 2026-10-28 11:39:57
- tomcat の起動 / 停止ログと catalina.log・catalina.out の違い 2026-10-28 10:24:23
- Tomcat の起動確認方法 5 つ|localhost:8080・ポート・プロセス・ログ 2026-10-28 10:24:23
- Oracle 19c COMMITの使い方: 暗黙コミット・自動確定・応答不明時の確認 2026-10-28 09:59:09
- Oracle 19c DELETE文|削除対象の確認・COMMIT前の判断・ROLLBACKの条件 2026-10-28 09:59:09
- Oracle 19c INSERT文|INSERT ALL・SELECT・RETURNINGとMERGEの使い分け 2026-10-28 09:59:09
- Oracle 19cのトランザクション制御|COMMIT・ROLLBACK・分離レベルと接続設定 2026-10-28 09:59:09