トランザクションやインデックス、ビューなどの基礎を学んだら、次のステップは「実践」です。この章では、現場を想定したSQL課題と、発想力を刺激するSQLパズルを紹介します。正確さ・可読性・最適化を意識しながら、ぜひ挑戦してみてください。
📝 実践課題:業務シナリオでSQLを書く
課題1:月次売上レポートを出力せよ
テーブル構成:
sales(id, product_id, amount, sale_date)products(id, name, category)
要件:
- 今月の売上合計をカテゴリごとに集計
- カテゴリ名と売上合計を降順で表示
-- あなたのSQL: SELECT ... FROM ... WHERE ... GROUP BY ... ORDER BY ... DESC;
課題2:ログインしないユーザーを抽出せよ
テーブル構成:
users(id, name, created_at)logins(user_id, login_at)
要件:
- 過去30日間にログインしていないユーザーを抽出
LEFT JOINとIS NULLを活用
-- あなたのSQL: SELECT ... FROM ... LEFT JOIN ... ON ... WHERE ...
課題3:同姓同名ユーザーの重複をチェックせよ
テーブル構成:
users(id, last_name, first_name)
要件:
- 同じ名前のユーザーが2人以上いる組み合わせを表示
- 姓・名ごとにグループ化し、件数をカウント
-- あなたのSQL: SELECT last_name, first_name, COUNT(*) as count FROM users GROUP BY last_name, first_name HAVING COUNT(*) >= 2;
🧠 SQLパズル:ロジックで解く頭の体操
パズル1:ランク付けせよ(RANK vs ROW_NUMBER)
テーブル: players(id, name, score)
問題: スコアの高い順に順位をつけ、同スコアは同順位とし、順位の飛び番も考慮すること。
-- RANKを使った例: SELECT name, score, RANK() OVER (ORDER BY score DESC) AS rank FROM players;
さらに応用: ROW_NUMBERとの違いを比較してみましょう。
パズル2:連続ログイン日数をカウントせよ
テーブル: logins(user_id, login_date)
問題: ユーザーごとに、連続ログインした最大日数を求めてください。
ヒント: 日付と連番を差し引いてグループ化する手法(ギャップアンドアイランド)を使います。
-- 難易度高:PostgreSQL用 SELECT user_id, COUNT(*) AS streak FROM ( SELECT user_id, login_date, login_date - INTERVAL '1 day' * ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS grp FROM logins ) AS t GROUP BY user_id, grp ORDER BY streak DESC;
パズル3:カレンダーを作れ
問題: 指定月の1日〜末日までの日付を行として出力してください(テーブルに日付が存在しない場合も含む)。
ヒント: 生成関数 generate_series()(PostgreSQL)や数値テーブルとのJOINを活用します。
-- PostgreSQL用 SELECT generate_series( DATE '2025-10-01', DATE '2025-10-31', INTERVAL '1 day' ) AS calendar_day;
MySQLの場合: 1〜31の数値テーブルをJOINして日付を作成する方法を使います。
💡 解答のチェックポイント
自分のSQLが正しいかどうかをチェックするには、次の点を意識しましょう:
- 出力結果は要件に合っているか?
- クエリの実行時間は許容範囲か?(特にJOINやウィンドウ関数)
- インデックスを活用できる書き方か?
- 可読性があるか?将来の自分が読んで理解できるか?
正解は1つではありません。「なぜその書き方をしたのか」を説明できるようになることが、エンジニアとしての実力アップに直結します。

