Python基礎

はじめてのSQL:SELECT/WHERE/GROUP BYを最短で理解【コピペOK】

データ分析・自動レポート・簡易アプリの土台はSQLです。Pythonだけで押し切るより、前処理の7割をDB側で完結させる方が速く・安定します。本記事は、未経験〜初学者が週10時間×2〜3週で、SELECT→WHERE→GROUP BY→JOIN→ウィンドウ関数→CTE→最適化(EXPLAIN/INDEX)までを“実務の型”で習得できるよう設計。すべてコピペで動くサンプルと、月末ミニプロジェクト、レビュー観点チェックリストを配布します。

この記事で身に付く力

  • 業務で崩れないSQLの“型”(抽出→集計→結合→窓関数→最適化)
  • 読める・直せるクエリの書き方(CTEで段階化/粒度の統一)
  • EXPLAIN/INDEXの最短実務運用(遅いSQLを早くする勘所)

SQLでつまずく典型パターン

最初の壁は“用語”ではなく“粒度”と“結合”。現場で頻発する落とし穴を先に知っておくと、学習効率が一気に上がります。

  • GROUP BYと集計関数の関係が曖昧で、想定外の行数になる。
  • INNER/LEFT JOINの使い分けを誤り、行が欠落する/爆増する。
  • ROW_NUMBERRANKなどウィンドウ関数PARTITION/ORDER設計がふわっとして誤集計
  • インデックスの基本があやふやで、遅いクエリを連発。

解決策は、「目的→テーブル関係→クエリの型」で考えること。最頻出パターンを型化すれば迷いが消えます。

[内部リンク:未経験からデータサイエンティストへ:6ヶ月ロードマップ]

現場10年で残った“読みやすく速いSQL”の作法

筆者(ふみと)は大手企業でデータ/マーケティングサイエンティスト10年。DWH・BI・MLの現場で読めるSQL=強いを痛感しました。ここでは商用DB/クラウドでも通用する書き方に寄せつつ、ローカルでも動かせるようSQLite/PostgreSQL準拠の構文を中心に紹介します(方言差は都度注釈)。

[内部リンク:【自己紹介】大手企業データサイエンティスト10年のキャリアと学び]

SQL“実務の型” 12ステップ

以降はSQLiteを想定。ファイル1つで動くので学習に最適です(PostgreSQLでも大筋同じ)。

ステップ0:サンプルDBの用意(売上とマスタ)

salesorder_date DATE, store TEXT, product TEXT, qty INT, price REAL)/storesstore TEXT PRIMARY KEY, area TEXT, open_date DATE)を作り、最小構成で練習します。

-- DB作成(sqlite3 ターミナル例)
-- sqlite3 retail.sqlite
CREATE TABLE IF NOT EXISTS sales (
  order_date TEXT,
  store      TEXT,
  product    TEXT,
  qty        INTEGER,
  price      REAL
);
CREATE TABLE IF NOT EXISTS stores (
  store     TEXT PRIMARY KEY,
  area      TEXT,
  open_date TEXT
);
-- 例データ(簡略)
INSERT INTO stores VALUES
 ('S01','Tokyo','2023-01-01'),('S02','Osaka','2023-02-01');
INSERT INTO sales VALUES
 ('2024-04-01','S01','A',2,500),('2024-04-02','S01','B',1,1200),
 ('2024-04-02','S02','A',3,480),('2024-05-01','S01','A',5,500);

1) SELECT/WHERE/ORDER/LIMIT:抽出の基本

まずはWHEREで行を絞る→SELECTで列を選ぶ→ORDERで並べる。先にフィルタ、後で表示が鉄則です。

-- 売上データの基本列と計算列(amount=qty*price)
SELECT order_date, store, product,
       qty, price,
       qty * price AS amount
FROM sales
WHERE qty > 0 AND price > 0
ORDER BY order_date ASC
LIMIT 10;

2) 集計(GROUP BY/HAVING):粒度を意識

集計は「何の1行か?」を先に決める。SELECTに出す非集計列はすべてGROUP BYへ。条件は行→WHERE、集計後→HAVING

-- 月×店舗の売上合計/数量
SELECT strftime('%Y-%m', order_date) AS ym,
       store,
       SUM(qty*price) AS sales,
       SUM(qty)       AS qty
FROM sales
WHERE qty>0 AND price>0
GROUP BY ym, store
HAVING SUM(qty) > 0
ORDER BY ym, store;

3) JOIN(INNER/LEFT):“欠落させない”結合

欠けて困る側をLEFTの左に。参照マスタのキー重複は事前に検査([内部リンク:pandas実践]の検査手法参照)。

-- 売上に店舗のエリアを付与(LEFTで欠落防止)
SELECT s.order_date, s.store, t.area,
       s.product, s.qty, s.price,
       s.qty*s.price AS amount
FROM sales AS s
LEFT JOIN stores AS t
  ON s.store = t.store;

4) サブクエリ/CTE:読みやすさの味方

複雑な集計はCTEで段階化FROM句サブクエリで局所的に処理すると読みやすいです。

-- CTEで月次集計→上位店舗を抽出
WITH monthly AS (
  SELECT strftime('%Y-%m', order_date) AS ym, store,
         SUM(qty*price) AS sales
  FROM sales
  GROUP BY ym, store
)
SELECT ym, store, sales
FROM (
  SELECT ym, store, sales,
         ROW_NUMBER() OVER(PARTITION BY ym ORDER BY sales DESC) AS rn
  FROM monthly
)
WHERE rn <= 3
ORDER BY ym, rn;

5) ウィンドウ関数:ランキング/移動平均/累積

PARTITION BY=グループ、ORDER BY=時間/順位の方向。LAG/LEAD/SUM OVERは最頻出。

-- 店舗別の売上累積と前月比
WITH m AS (
  SELECT strftime('%Y-%m', order_date) AS ym, store,
         SUM(qty*price) AS sales
  FROM sales
  GROUP BY ym, store
)
SELECT ym, store, sales,
       SUM(sales) OVER(PARTITION BY store ORDER BY ym) AS sales_cum,
       LAG(sales, 1) OVER(PARTITION BY store ORDER BY ym) AS sales_prev,
       ROUND( (sales - LAG(sales,1) OVER(PARTITION BY store ORDER BY ym))
              / NULLIF(LAG(sales,1) OVER(PARTITION BY store ORDER BY ym),0), 4) AS yoy
FROM m
ORDER BY store, ym;

6) CASE式:条件分岐(ラベル付け/バケット)

可読性のため、閾値やラベルは上から下へ。後で辞書テーブル化も検討。

SELECT product,
       CASE
         WHEN price >= 1000 THEN 'High'
         WHEN price >= 500  THEN 'Mid'
         ELSE 'Low'
       END AS price_band,
       COUNT(*) AS cnt
FROM sales
GROUP BY product, price_band;

7) 重複/NULL/文字列処理の定石

NULLは早めに埋める/除外。文字列の正規化は入力時 or 取り出し直後

-- 重複除去
SELECT DISTINCT store FROM sales;

\-- NULLを0に
SELECT IFNULL(SUM(qty), 0) FROM sales;

\-- 文字列整形(SQLite)
SELECT TRIM(REPLACE(product, ' ', '' )) AS product\_norm FROM sales;

8) セット演算(UNION/INTERSECT/EXCEPT)

集合演算は存在比較に強い。UNION ALLは重複許容、UNIONは重複削除。

-- 2期間の共通/差分店舗(例)
WITH s1 AS (
  SELECT DISTINCT store FROM sales WHERE order_date BETWEEN '2024-04-01' AND '2024-04-30'
), s2 AS (
  SELECT DISTINCT store FROM sales WHERE order_date BETWEEN '2024-05-01' AND '2024-05-31'
)
SELECT 'common' AS kind, store FROM (
  SELECT store FROM s1 INTERSECT SELECT store FROM s2
)
UNION ALL
SELECT 'only_apr', store FROM (
  SELECT store FROM s1 EXCEPT SELECT store FROM s2
)
UNION ALL
SELECT 'only_may', store FROM (
  SELECT store FROM s2 EXCEPT SELECT store FROM s1
);

9) パフォーマンス基本:EXPLAINとインデックス

フィルタ/結合キーにインデックス。複合は選択性の高い列→範囲列の順が目安。

-- 実行計画(SQLite)
EXPLAIN QUERY PLAN
SELECT *
FROM sales
WHERE store='S01' AND order_date BETWEEN '2024-04-01' AND '2024-04-30';

\-- 複合インデックス(フィルタ順に)
CREATE INDEX IF NOT EXISTS idx\_sales\_store\_date ON sales(store, order\_date);

10) 取引(トランザクション)と排他の最短メモ

一貫性の必要な一連の変更BEGIN…COMMITでまとめる。失敗時はROLLBACK

BEGIN TRANSACTION;
  INSERT INTO sales VALUES('2024-06-01','S02','B',4,1200);
  UPDATE stores SET area='Kansai' WHERE store='S02';
COMMIT;

11) Python連携:pandas×SQLiteで実務導線

重い整形はSQL最終可視化/配布はPython/Excelが最速です。

import sqlite3, pandas as pd
con = sqlite3.connect("retail.sqlite")
q = """
WITH monthly AS (
  SELECT strftime('%Y-%m', order_date) AS ym, store,
         SUM(qty*price) AS sales, SUM(qty) AS qty
  FROM sales
  WHERE qty>0 AND price>0
  GROUP BY ym, store
)
SELECT * FROM monthly;
"""
monthly = pd.read_sql_query(q, con)
monthly.to_excel("report.xlsx", index=False)
con.close()

12) 品質保証:レビュー観点チェックリスト

提出前の最終チェックに。レビュー観点が明文化されていると事故が減ります。

  • 粒度が明確(行=何の1行か?)
  • INNER/LEFTの選択根拠がある(欠落の許容
  • GROUP BY列と集計関数の対応が正しい
  • ウィンドウ関数PARTITION/ORDERが妥当
  • インデックスの有無(結合キー/フィルタ列)を確認
  • CTEで段階化され、読みやすい

スクール伴走で“仕上げ”まで最短到達

独学でもいけますが、設計レビュー質問対応があると、JOINやウィンドウの事故率が激減。6ヶ月ロードマップのポートフォリオに直結させるなら、下記2校の無料カウンセリングから始めるのが近道です。

  • 株式会社キカガク業務再現型の課題設計と出口支援が強い。転職直結に◎。
  • Tech Academy質問の速さ×短時間運用が強み。副業/在宅と相性◎。

TechAcademy データサイエンスコース(受講料:174,600円~ ※更に割引あり)

TechAcademy 無料相談

株式会社キカガク AI人材長期育成コース(受講料:237,600円~)

キカガク 無料相談

行動:ミニプロジェクト(提出推奨)

課題:「月次×店舗の売上トレンド上位3店舗前年同月比を1本のSQLで出力し、report.xlsxに保存」。

  1. salesからamount=qty*priceym=strftime('%Y-%m', order_date)を作成。
  2. WITH monthly AS (...)で月次集計テーブルを作る。
  3. ウィンドウでROW_NUMBER()LAG()を使い、上位3/前年比を併記。
  4. Pythonでpd.read_sql_query→Excel出力。
  5. READMEに再現手順示唆(3行)を記載。
WITH monthly AS (
  SELECT strftime('%Y-%m', order_date) AS ym,
         store,
         SUM(qty*price) AS sales
  FROM sales
  WHERE qty>0 AND price>0
  GROUP BY ym, store
), ranked AS (
  SELECT ym, store, sales,
         ROW_NUMBER() OVER(PARTITION BY ym ORDER BY sales DESC) AS rn,
         LAG(sales,1) OVER(PARTITION BY store ORDER BY ym) AS prev
  FROM monthly
)
SELECT ym, store, sales, rn,
       ROUND( (sales - prev) / NULLIF(prev,0), 4) AS yoy
FROM ranked
WHERE rn <= 3
ORDER BY ym, rn;
[ ] 粒度とJOIN/LEFTの根拠が言語化されている
[ ] GROUP BY/HAVING/WHEREの使い分けが正しい
[ ] PARTITION/ORDERが要件に合致
[ ] 実行計画(EXPLAIN)でフルスキャンを避けられている
[ ] 再現手順(DB作成→SQL→出力)がREADMEにある

付録A:SQLチートシート(保存版)

-- 1) Top-N per group
SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER(PARTITION BY group_col ORDER BY score DESC) AS rn
  FROM t
) WHERE rn<=N;

\-- 2) ピボット風(集計を列化)
SELECT ym,
SUM(CASE WHEN store='S01' THEN sales ELSE 0 END) AS S01,
SUM(CASE WHEN store='S02' THEN sales ELSE 0 END) AS S02
FROM monthly GROUP BY ym;

\-- 3) EXISTS で高速な存在判定
SELECT \* FROM t1 WHERE EXISTS (
SELECT 1 FROM t2 WHERE t2.key=t1.key
);

\-- 4) 日付関数(SQLite)
SELECT date(order\_date,'start of month') AS first\_day FROM sales;

付録B:SQLスタイルガイド(最短版)

  • すべて大文字キーワード小文字テーブル/列で可読性UP
  • 列は論理順に並べる(キー→属性→計算列)
  • CTE名/エイリアスは意味を持たせる(monthly, ranked
  • コメントは節ごとに(-- 目的 / -- 集計 / -- ランキング
  • 1行は100文字前後、句ごとに改行

付録C:安全な実装のために

  • アプリからはプレースホルダでパラメータ化(SQLインジェクション防止)
  • 本番DBは読み取り専用アカウントで参照
  • 個人情報は最小限の列だけ取得、マスキング運用

この記事から次に読むべきもの(内部リンク)

SQLite
【保存版】SQLite×Pythonで作る“ローカルDWH”——ETL・集計・レポート自動化の最短手順

ローカルでゼロ構築、ファイル1つで完結、サーバ不要。本記事はSQLite×Pythonで“毎日回る”ETL・集計・レポート自動化を最短で作るための完全ガイドです。データ設計→DB作成→ETL(取り込み ...

スキルアップ
【実務で差がつく】pandas実践:欠損処理・結合・ウィンドウ関数・時系列・品質保証まで“読みやすく速い”型を習得

リード(結論)基礎を終えたら次は実務の現場で頻出する処理を“型”で覚える段階です。本記事は、pandas 2.x を前提に、欠損・外れ値・結合・ウィンドウ関数・時系列・カテゴリ処理・集計の自動化・大規 ...

可視化
【保存版】可視化入門:Matplotlib/Plotlyの使い分けと“伝わるグラフ設計”10ステップ

結論:可視化は「きれいに描く」ことではなく、意思決定を動かすための設計です。本稿では、未経験〜初学者が 週10時間×1〜2週 で、Matplotlib/Plotlyを軸に “伝わるグラフ”の設計と実装 ...

データレポート納品
【保存版】データレポート納品の型:要件定義→ETL→検証→可視化→Excel/PDF→引き継ぎまで、失注しないワークフロー完全版

“いい分析”より“伝わる納品”。副業や実務で評価されるのは、意思決定に効く1枚と再現できるパッケージを期限通り出せること。 本記事は、未経験〜初学者が週10時間×2〜3週で、要件定義→データ受領→ET ...

機械学習
【保存版】scikit-learn基礎:回帰・分類・前処理・パイプライン・交差検証を“実務の型”で習得

機械学習で迷子になる最大の理由は、前処理→学習→評価→改善の順番が曖昧なまま個々のアルゴリズムに飛びつくこと。本記事は、未経験〜初学者が週10時間×2〜3週で到達できるscikit-learnの最短ル ...

最近のコメント

    • この記事を書いた人
    • 最新記事

    ふみと

    このブログでは、データサイエンティストとして市場価値を上げる方法を独自にまとめて発信しています。

    【プロフィール】
    ・大手企業データサイエンティスト/マーケティングサイエンティスト(10年、年収900万円台)/案件100件以上
    ・資格:JDLA E資格(日本ディープラーニング協会主催)/JDLA Community(CDLE会員)/Advanced Marketer/ビジネス統計スペシャリスト/統計検定2級/TOEIC 805
    ・スキル:Python/Tableau/SQL/機械学習/Deep Learning/RPA

    -Python基礎