SQL SELECT文のテンプレ8選 ― 集計・JOIN・サブクエリですぐ使えるパターン

業務でよく使うSQLのSELECT文テンプレを8パターン収録。GROUP BY、JOIN、CASE、ウィンドウ関数までコピペで動かせます。

SQLは「毎回ちょっとずつ違う書き方になる」言語の代表選手だと思ってます(- -;

「あれ、JOINの構文どっちだっけ…」とリファレンスを開く時間、累計するとそこそこ消えてるはず。

そこで自分の引き出し用に、業務頻出パターンをここに集約しました。
シーン別に「これに当てはまる」と即座に判断できると、SQLを書く時間がそれなりに短くなります。

気をつけたいのは下記の3点。

下記はPostgreSQL想定で書いてますが、MySQLでもほぼそのまま動くようにしています!

1. 基本のWHERE句で絞り込み

用途: 特定条件のレコードを取得する基本パターン

SELECT
  id,
  name,
  email,
  created_at
FROM
  users
WHERE
  status = 'active'
  AND created_at >= '2026-01-01'
ORDER BY
  created_at DESC
LIMIT 100;

基本パターンは **「カラム明示」+「WHERE条件」+「ORDER BY」+「LIMIT」**の4点セットでOK。

SELECT * は便利なんですが、運用に入ってから問題に気付くケースがけっこうあって、自分も以前 SELECT * で取った結果をそのまま JSON レスポンスにしていたら、ある日カラム追加された途端にPIIっぽい項目がそのままAPIから漏れかけたことがあるんですよね…。それ以来、カラム明示はクセにしています。

2. GROUP BY で集計

用途: カテゴリ別の件数・合計を出すとき

SELECT
  category,
  COUNT(*) AS total_count,
  SUM(amount) AS total_amount,
  AVG(amount) AS avg_amount
FROM
  orders
WHERE
  created_at >= '2026-01-01'
GROUP BY
  category
ORDER BY
  total_count DESC;

集計は COUNTSUMAVG をAS でわかりやすいエイリアスにするのがコツ。

レポート用のSQLだと特に「何の数字か」が分からないと困るので、命名は丁寧に(^^!

3. INNER JOIN で関連テーブル結合

用途: 関連テーブルから情報を取得するとき

SELECT
  u.id AS user_id,
  u.name AS user_name,
  o.id AS order_id,
  o.amount AS order_amount
FROM
  users u
INNER JOIN
  orders o ON u.id = o.user_id
WHERE
  u.status = 'active'
ORDER BY
  o.created_at DESC;

JOINは **テーブル別名(u, o)**で短く書くと読みやすいです。

INNER JOINだと両テーブルにマッチするレコードだけが取れるので、片方にしか無いデータも欲しいときは LEFT JOIN を使います(^^b

4. LEFT JOIN で欠損も含める

用途: 注文のないユーザーも含めて取得するとき

SELECT
  u.id,
  u.name,
  COUNT(o.id) AS order_count
FROM
  users u
LEFT JOIN
  orders o ON u.id = o.user_id
GROUP BY
  u.id, u.name
ORDER BY
  order_count DESC;

LEFT JOIN + COUNT で「ゼロ注文ユーザー」も含めた集計ができます。

INNER JOINだと注文ゼロのユーザーが消えるので、リテンション分析などはLEFT必須です(^^!

5. EXISTSでサブクエリ

用途: 「ある条件を満たす関連レコードがあるか」で絞り込む

SELECT
  u.id,
  u.name,
  u.email
FROM
  users u
WHERE
  EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.user_id = u.id
      AND o.amount >= 10000
  );

EXISTSは **「該当データが1件でもあれば」**という条件チェック専用。

JOINで集計するよりパフォーマンスが良いケースが多いので、データ量が大きい時はEXISTSが有利になるパターンが多いです(^^b

6. CASE WHEN で条件分岐

用途: 値に応じて別のカテゴリを付与するとき

SELECT
  id,
  name,
  amount,
  CASE
    WHEN amount >= 100000 THEN '高額'
    WHEN amount >= 10000 THEN '中額'
    ELSE '少額'
  END AS amount_category
FROM
  orders
ORDER BY
  amount DESC;

CASE WHENは **「複数条件の分岐ラベリング」**が圧倒的に便利。

レポート集計用に「金額帯別」「年齢層別」など作るときに重宝します(^^!

7. ウィンドウ関数で順位付け

用途: グループごとの順位を取得するとき

SELECT
  user_id,
  order_id,
  amount,
  ROW_NUMBER() OVER (
    PARTITION BY user_id
    ORDER BY amount DESC
  ) AS rank_in_user
FROM
  orders;

ウィンドウ関数は **「グループ内の順位や移動平均」**を出すのに重宝します。

ROW_NUMBER() の他に RANK()DENSE_RANK()SUM() OVER (...) などバリエーション豊富です(^^b

8. ページネーション(OFFSET と LIMIT)

用途: 検索結果を1ページ20件ずつ取得するとき

SELECT
  id,
  name,
  created_at
FROM
  users
WHERE
  status = 'active'
ORDER BY
  created_at DESC
LIMIT 20 OFFSET 40;

OFFSETは **「最初の40件をスキップして、次の20件」**という意味。

ただしOFFSETが大きくなるとパフォーマンスが落ちるので、巨大データなら カーソルベースのページネーションWHERE created_at < ? )への移行を検討します(- -;