0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

実務でよく使うSQL 10選【JOIN・集計・ウィンドウ関数】

0
Posted at

はじめに

SQLはSELECT * FROM usersは書けても、「先月比を出す」「顧客ごとの最新注文だけ取る」といった実務の要求になると急に難しくなります。

この記事では、実務で頻出するSQLパターンを10個まとめました。売上・注文データを例に、コピペして応用できる形で紹介します。

サンプルテーブル

以下の3テーブルを前提にします。

-- 顧客
customers (customer_id, name, email, created_at)

-- 注文
orders (order_id, customer_id, order_date, total_amount, status)

-- 注文明細
order_items (item_id, order_id, product_name, category, quantity, price)

① 複数テーブルを結合する(JOIN)

最も基本かつ重要な操作です。

SELECT
    o.order_id,
    c.name AS customer_name,
    o.order_date,
    o.total_amount
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= '2026-04-01'
ORDER BY o.order_date DESC;

JOINの種類

種類 動作
INNER JOIN(= JOIN) 両方に存在するデータのみ
LEFT JOIN 左テーブルは全件、右がなければNULL
RIGHT JOIN 右テーブルは全件(あまり使わない)

使い分けの目安: 「注文がない顧客も含めたい」なら LEFT JOIN、「注文がある顧客だけでいい」なら JOIN。


② 注文がない顧客を抽出する

LEFT JOIN + IS NULL の組み合わせで「片方にだけ存在するデータ」を取れます。

SELECT
    c.customer_id,
    c.name,
    c.email
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;

休眠顧客の抽出やデータ不整合のチェックで頻繁に使います。


③ グループごとに集計する(GROUP BY)

SELECT
    c.name,
    COUNT(o.order_id)      AS 注文回数,
    SUM(o.total_amount)    AS 累計購入額,
    AVG(o.total_amount)    AS 平均単価,
    MAX(o.order_date)      AS 最終注文日
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name
ORDER BY 累計購入額 DESC;

よくあるエラー: SELECT に書いた非集計カラムは、すべて GROUP BY に含める必要があります(MySQLは設定によっては通りますが、明示するのが安全です)。


④ 集計結果で絞り込む(HAVING)

WHEREは集計前、HAVINGは集計後の絞り込みです。

-- 3回以上注文している顧客だけ
SELECT
    c.name,
    COUNT(o.order_id) AS 注文回数
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name
HAVING COUNT(o.order_id) >= 3
ORDER BY 注文回数 DESC;
-- WHERE と HAVING の併用
SELECT
    c.name,
    SUM(o.total_amount) AS 売上合計
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE o.status = 'completed'          -- 集計前:完了した注文のみ
GROUP BY c.customer_id, c.name
HAVING SUM(o.total_amount) >= 100000  -- 集計後:合計10万以上

⑤ 月別に集計する

日付を月単位にまとめるのは、レポート作成の定番です。

-- MySQL
SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS 年月,
    COUNT(*)                          AS 注文件数,
    SUM(total_amount)                 AS 売上合計
FROM orders
WHERE status = 'completed'
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY 年月;
-- PostgreSQL
SELECT
    TO_CHAR(order_date, 'YYYY-MM') AS 年月,
    COUNT(*)                        AS 注文件数,
    SUM(total_amount)               AS 売上合計
FROM orders
WHERE status = 'completed'
GROUP BY TO_CHAR(order_date, 'YYYY-MM')
ORDER BY 年月;

⑥ 条件によって値を振り分ける(CASE)

SELECT
    name,
    total_purchase,
    CASE
        WHEN total_purchase >= 500000 THEN 'プラチナ'
        WHEN total_purchase >= 100000 THEN 'ゴールド'
        WHEN total_purchase >= 10000  THEN 'シルバー'
        ELSE 'ブロンズ'
    END AS 会員ランク
FROM (
    SELECT
        c.name,
        SUM(o.total_amount) AS total_purchase
    FROM customers c
    JOIN orders o ON c.customer_id = o.customer_id
    GROUP BY c.customer_id, c.name
) AS summary;

CASEで横持ち集計(クロス集計)

-- カテゴリを列にして集計する
SELECT
    DATE_FORMAT(o.order_date, '%Y-%m') AS 年月,
    SUM(CASE WHEN oi.category = '食品' THEN oi.price * oi.quantity ELSE 0 END) AS 食品,
    SUM(CASE WHEN oi.category = '飲料' THEN oi.price * oi.quantity ELSE 0 END) AS 飲料,
    SUM(CASE WHEN oi.category = '雑貨' THEN oi.price * oi.quantity ELSE 0 END) AS 雑貨
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
GROUP BY DATE_FORMAT(o.order_date, '%Y-%m')
ORDER BY 年月;

Excelのピボットテーブルのような表がSQLだけで作れます。


⑦ 顧客ごとの最新注文だけを取得する

「グループごとの最新1件」は頻出ですが、意外と書き方に迷うパターンです。

方法A:ウィンドウ関数(MySQL 8.0+ / PostgreSQL)

SELECT *
FROM (
    SELECT
        o.*,
        ROW_NUMBER() OVER (
            PARTITION BY o.customer_id
            ORDER BY o.order_date DESC
        ) AS rn
    FROM orders o
) AS ranked
WHERE rn = 1;

PARTITION BY で顧客ごとに区切り、その中で日付順に番号を振って1番目だけ取る、という考え方です。

方法B:サブクエリ(古いMySQLでも動く)

SELECT o.*
FROM orders o
JOIN (
    SELECT customer_id, MAX(order_date) AS latest
    FROM orders
    GROUP BY customer_id
) AS m
  ON o.customer_id = m.customer_id
 AND o.order_date  = m.latest;

⑧ ランキングを出す(ウィンドウ関数)

SELECT
    product_name,
    SUM(quantity * price) AS 売上,
    RANK() OVER (ORDER BY SUM(quantity * price) DESC) AS 順位
FROM order_items
GROUP BY product_name;

RANK / DENSE_RANK / ROW_NUMBER の違い

同じ売上が並んだときの挙動が異なります。

関数 同順位のとき
ROW_NUMBER() 1, 2, 3, 4(必ず連番)
RANK() 1, 2, 2, 4(次を飛ばす)
DENSE_RANK() 1, 2, 2, 3(次を飛ばさない)

⑨ 前月比・前日比を出す(LAG)

LAG() は「1つ前の行の値」を取得する関数です。これで期間比較が簡単に書けます。

SELECT
    年月,
    売上,
    LAG(売上) OVER (ORDER BY 年月) AS 前月売上,
    売上 - LAG(売上) OVER (ORDER BY 年月) AS 増減,
    ROUND(
        (売上 - LAG(売上) OVER (ORDER BY 年月))
        / LAG(売上) OVER (ORDER BY 年月) * 100,
        1
    ) AS 増減率
FROM (
    SELECT
        DATE_FORMAT(order_date, '%Y-%m') AS 年月,
        SUM(total_amount)                 AS 売上
    FROM orders
    WHERE status = 'completed'
    GROUP BY DATE_FORMAT(order_date, '%Y-%m')
) AS monthly
ORDER BY 年月;

これまでExcelでやっていた前月比の計算が、SQL1本で完結します。


⑩ 累計を出す(SUM OVER)

SELECT
    年月,
    売上,
    SUM(売上) OVER (ORDER BY 年月) AS 累計売上
FROM (
    SELECT
        DATE_FORMAT(order_date, '%Y-%m') AS 年月,
        SUM(total_amount)                 AS 売上
    FROM orders
    GROUP BY DATE_FORMAT(order_date, '%Y-%m')
) AS monthly
ORDER BY 年月;

ORDER BY を付けた SUM() OVER は「そこまでの累計」になります。年度累計や進捗管理に便利です。


書くときのコツ

① 複雑なクエリはCTE(WITH句)で分解する

サブクエリのネストが深くなったら、WITH で名前を付けて分けると格段に読みやすくなります。

WITH monthly_sales AS (
    SELECT
        DATE_FORMAT(order_date, '%Y-%m') AS 年月,
        SUM(total_amount)                 AS 売上
    FROM orders
    WHERE status = 'completed'
    GROUP BY DATE_FORMAT(order_date, '%Y-%m')
)
SELECT
    年月,
    売上,
    LAG(売上) OVER (ORDER BY 年月) AS 前月売上
FROM monthly_sales
ORDER BY 年月;

処理の流れが上から下に読めるようになります。

② まず小さく作って確かめる

いきなり完成形を書かず、SELECT で対象データを確認 → WHERE で絞る → GROUP BY を足す、と段階的に組み立てるとミスが減ります。

③ 遅いと感じたらEXPLAINを見る

パフォーマンスが気になったらMySQLが遅い時に最初に確認する5つのことも参考にしてください。


まとめ

実務でよく使うSQLパターン10選でした。

  1. JOINで結合
  2. LEFT JOIN + IS NULLで「ない方」を抽出
  3. GROUP BYで集計
  4. HAVINGで集計後に絞り込み
  5. 日付を月単位にまとめる
  6. CASEで条件分岐・クロス集計
  7. グループごとの最新1件を取る
  8. RANKでランキング
  9. LAGで前月比
  10. SUM OVERで累計

特に**ウィンドウ関数(⑦〜⑩)**を使えるようになると、これまでExcelに出力してから計算していた処理がSQLだけで完結するようになります。


SQL・データ集計のご相談

「複雑な集計クエリを組みたい」「レポート作成を自動化したい」「DBのパフォーマンスを改善したい」などのご相談を受け付けています。

🌐 https://datarou.com

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?