メインコンテンツへ移動
SEMentor
データベース 約12分

SQL 基礎 — SELECT・JOIN・集計

実務で必須のSQLを体系的に学ぶ。SELECT・WHERE・JOIN・GROUP BYの基礎から応用まで

SQL とは

SQL(Structured Query Language)はリレーショナルデータベースを操作するための標準言語です。大きく 3 種類のコマンドがあります。

分類コマンド用途
DMLSELECT / INSERT / UPDATE / DELETEデータ操作
DDLCREATE / ALTER / DROPテーブル定義
DCLGRANT / REVOKE権限管理

SELECT の基本

SELECT 列名1, 列名2
FROM   テーブル名
WHERE  条件
ORDER BY 列名 ASC|DESC
LIMIT  件数;

実例:ユーザー一覧

-- 全列取得(開発時のみ。本番では列を明示)
SELECT * FROM users;

-- 特定の列だけ取得
SELECT id, name, email FROM users;

-- 条件で絞り込む
SELECT * FROM users
WHERE created_at >= '2026-01-01'
  AND is_active = true;

-- 新しい順に 10 件
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 10;

文字列・数値・日付フィルタ

-- LIKE:部分一致(%は任意の文字列)
SELECT * FROM products WHERE name LIKE '%スマホ%';

-- IN:複数の値のどれかに一致
SELECT * FROM orders WHERE status IN ('pending', 'processing');

-- BETWEEN:範囲
SELECT * FROM sales
WHERE amount BETWEEN 1000 AND 50000;

-- IS NULL / IS NOT NULL
SELECT * FROM users WHERE deleted_at IS NULL;

集計関数と GROUP BY

-- 件数
SELECT COUNT(*) FROM orders;

-- 合計・平均・最大・最小
SELECT
  SUM(amount)  AS 売上合計,
  AVG(amount)  AS 平均単価,
  MAX(amount)  AS 最高額,
  MIN(amount)  AS 最低額
FROM orders;

-- ユーザーごとの注文数と合計金額
SELECT
  user_id,
  COUNT(*)      AS 注文数,
  SUM(amount)   AS 合計金額
FROM orders
GROUP BY user_id
ORDER BY 合計金額 DESC;

-- HAVING:GROUP BY 後の絞り込み(WHERE は集計前)
SELECT user_id, COUNT(*) AS cnt
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 3;  -- 3回以上注文したユーザーのみ

JOIN — テーブルの結合

INNER JOIN(内部結合)

両テーブルに存在する行だけを返します。

SELECT
  o.id          AS 注文ID,
  u.name        AS 顧客名,
  o.amount      AS 金額,
  o.ordered_at  AS 注文日時
FROM orders  o
INNER JOIN users u ON o.user_id = u.id
WHERE o.ordered_at >= '2026-04-01';

LEFT JOIN(左外部結合)

左テーブルの全行を返し、右テーブルにマッチしなければ NULL。

-- 注文がないユーザーも含む
SELECT
  u.name,
  COUNT(o.id) AS 注文数
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name
ORDER BY 注文数 DESC;

複数テーブルの結合

SELECT
  o.id,
  u.name       AS 顧客,
  p.name       AS 商品,
  oi.quantity  AS 数量
FROM orders     o
INNER JOIN users        u  ON u.id  = o.user_id
INNER JOIN order_items  oi ON oi.order_id = o.id
INNER JOIN products     p  ON p.id  = oi.product_id;

サブクエリ

-- 平均より高い注文を抽出
SELECT * FROM orders
WHERE amount > (SELECT AVG(amount) FROM orders);

-- EXISTS:関連レコードが存在するか
SELECT * FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.id
);

ウィンドウ関数(分析関数)

集計しながら元の行を保持できる強力な機能。

-- 各ユーザーの注文を金額降順でランク付け
SELECT
  user_id,
  id,
  amount,
  RANK() OVER (PARTITION BY user_id ORDER BY amount DESC) AS rank
FROM orders;

-- 累積合計(売上推移)
SELECT
  ordered_at::date AS 日付,
  SUM(amount)      AS 日次売上,
  SUM(SUM(amount)) OVER (ORDER BY ordered_at::date) AS 累積売上
FROM orders
GROUP BY 日付
ORDER BY 日付;

パフォーマンスの基本

-- EXPLAIN ANALYZE でクエリの実行計画を確認
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 123;

-- Seq Scan(全件スキャン)が出たらインデックス不足の可能性
-- → CREATE INDEX idx_orders_user_id ON orders(user_id);

実務メモ:N+1 問題(ループ内でクエリを実行)は JOIN または IN でまとめて解決する。

まとめ

  • SELECT + WHERE + ORDER BY + LIMIT が基本形
  • GROUP BY + HAVING で集計・絞り込み
  • INNER JOIN は共通レコード、LEFT JOIN は左テーブル全件
  • サブクエリとウィンドウ関数で複雑な分析も対応
  • EXPLAIN ANALYZE でボトルネックを特定する習慣を

このレッスンは未完了です。

次のレッスンへ