🔖 キーワード索引
#SELECT #JOIN #GROUP BY #Window関数 #RDBMS #宣言型
「sql 」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「sql」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
SQL SELECT JOIN GROUP BY WHERE RDBMS サブクエリ CTE ウィンドウ関数
これらのキーワードは「sql の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
💡 30秒で分かる結論
🍰 まずはやさしく
SQLはデータを取り出すための言葉です。
表形式のデータを操作するために使います。
スマホアプリのユーザー管理などで使われます。
ここではSQLでできることを学びます。
SQL :リレーショナルDBの操作言語
表形式データの問い合わせ・更新 のための言語。
主要命令:SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, JOIN。
宣言型 言語:「何が欲しい」を書けば、 DB エンジンが「どう取るか」を最適化。
拡張:Window 関数・CTE・JSON 操作 などモダン SQL は強力。
📍 文脈ボックス
🍰 まずはやさしく
SQLはデータベースを動かす道具です。
大量のデータから必要な情報を集めます。
都道府県ごとの統計データをまとめます。
ここでは書き方と実行される順番を学びます。
この用語は データエンジニアリング カテゴリに属します。 関連する別称・略号:(なし) 。
論文・実務レポートで SQL が登場したら、 まず本ページの「30秒で分かる結論」と「直感で掴む」を読めば、 その文脈で何を言っているか把握できます。
本ページでは「sql」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。
SQL (Structured Query Language) はリレーショナル DB を操作するための宣言型言語で、 SELECT/JOIN/GROUP BY を組み合わせて 47 都道府県・複数年の SSDSE-B-2026 から集計表を取り出せる。 本ページは構文の組み立て方、 実行順 (FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY) と性能上の落とし穴を扱う。
🎨 直感で掴む
🍰 まずはやさしく
SQLは注文書のようなものです。
欲しいデータの条件を伝えるために使います。
東京の出生数だけを抜き出す時に便利です。
ここでは条件の書き方を具体的に学びます。
「東京都の 2026 年の出生数を求めたい」 ── これを 1 行で書けるのが SQL。 SELECT 出生数 FROM 都道府県 WHERE 名前='東京' AND 年=2026;。 命令の順序ではなく「結果の条件」を宣言するので、 同じ問い合わせを DB が状況に応じて最適化して実行する。 アナリストの 8 割の仕事はこの SQL で書ける。
🔬 数式を言葉で読み解く
数式に出てくる記号の意味を 1 つずつ確認しましょう。
SELECT
返す列を指定。
FROM
対象のテーブル。
WHERE
行レベルの絞り込み条件。
GROUP BY
集約のキー。
HAVING
集約結果に対する絞り込み。
JOIN
複数テーブルを結合する。
🔬 RDBMS 種類の精密比較
SQL は ANSI 標準ですが、 実装によって細かな差があります。 4 大 RDBMS の特徴比較表。
RDBMS
特徴
向く用途
ライセンス
PostgreSQL 機能豊富、 SQL 準拠度高 汎用、 分析、 GIS OSS
MySQL 高速、 普及度高 Web アプリ OSS
SQLite サーバレス、 軽量 埋め込み、 単機テスト パブリックドメイン
DuckDB 分析特化、 列指向 データサイエンス OSS
BigQuery クラウド、 ペタバイト級 DWH 商用(GCP)
Snowflake クラウド DWH DWH、 BI 商用
Oracle エンタープライズ歴史長 基幹システム 商用
🧮 実値で計算してみる
SSDSE-B のような表形式データに対する典型的な集計 SQL。
STEP 1
SELECT 列の選択
SELECT 都道府県, 人口, 出生数
STEP 2
条件で絞る
WHERE 年 = 2026
STEP 4
並べ替え
ORDER BY 人口 DESC LIMIT 5
🧮 SSDSE-B-2026 を題材にした SQL 深掘り演習
SQL の真価は「実データに対して問い (クエリ) を投げ、 集計・抽出・比較を一括で行う 」場面で発揮される。 ここでは SSDSE-B-2026 (独立行政法人統計センターが公開している教育用標準データ、 47 都道府県 × 12 年 (2012〜2023) × 112 列)を題材に、 SQL の基本構文 (SELECT / WHERE / GROUP BY / JOIN / ウィンドウ関数) を実データ前提で復習する。 SSDSE-B は CSV で配布されているため、 SQLite に .import するだけで本格的な SQL 検証環境になる。
1. データ取り込み (CSV → SQLite)
このコードでやること : SSDSE-B-2026 の CSV を SQLite データベース ssdse_b.db にロードし、 主キー (year, pref_code) を持つテーブル ssdse_b を作る。 以後の SQL はすべてこのテーブルを参照する。
📥 入力データ (data/raw/SSDSE-B-2026.csv の先頭 4 行抜粋):
SSDSE-B-2026,Code,Prefecture,A1101,...,A4101
2020,R01000,北海道,5224614,...,29523
2020,R02000,青森県,1237984,...,6837
2020,R03000,岩手県,1210534,...,6718
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 -- SQLite シェル ( sqlite3 ssdse_b . db ) で実行
. mode csv
. headers on
. import data / raw / SSDSE - B - 2026. csv ssdse_b_raw
-- 型をつけて整形 ( 年度 / Code を主キー扱いに )
CREATE TABLE ssdse_b AS
SELECT
CAST ( "SSDSE-B-2026" AS INTEGER ) AS year ,
Code AS pref_code ,
Prefecture AS pref_name ,
CAST ( A1101 AS INTEGER ) AS population ,
CAST ( A4101 AS INTEGER ) AS births
FROM ssdse_b_raw
WHERE "SSDSE-B-2026" GLOB '[0-9][0-9][0-9][0-9]' ;
CREATE UNIQUE INDEX idx_ssdse_b_pk ON ssdse_b ( year , pref_code );
SELECT COUNT ( * ) FROM ssdse_b ;
📤 実行例 (2012–2023 の 12 年分 × 47 都道府県 = 564 行):
COUNT(*)
564
💬 結果の読み方 : 564 行 = 47 都道府県 × 12 年 (2012〜2023 年)。 ここで件数を確認するのは、 CSV のエンコーディングや空行で取り込みが欠ける事故を未然に検知するための基本動作。 SQL の現場では「最初に必ず COUNT(*)」が鉄則である。
2. 散布図で見る人口と出生数の関係 (基本 SELECT)
このコードでやること : 2020 年の都道府県データを SQL で抽出し、 Python (pandas) 側で散布図に描く。 SQL 側は「WHERE year = 2020」で 47 行に絞り、 不要な列を捨てるだけ。
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行)
都道府県 A1101(総人口) A4101(出生数)
北海道 5,092,000 24,430
東京都 14,086,000 86,348
沖縄県 1,468,000 12,549
…(全 47 行)
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 # ── この例で使うデータベースを、SSDSE の CSV から作ります ──
# (ssdse_b.db は同梱していないので、ここで組み立てます)
import sqlite3
import pandas as pd
_src = pd . read_csv ( 'data/raw/SSDSE-B-2026.csv' , encoding = 'cp932' , header = 0 )
_src = _src [ _src [ 'Code' ] . astype ( str ) . str . match ( r '^R\d {5} $' , na = False )] . copy ()
_tbl = pd . DataFrame ({
'year' : pd . to_numeric ( _src [ 'SSDSE-B-2026' ], errors = 'coerce' ),
'pref_name' : _src [ 'Prefecture' ],
'population' : pd . to_numeric ( _src [ 'A1101' ], errors = 'coerce' ),
'births' : pd . to_numeric ( _src [ 'A4101' ], errors = 'coerce' ),
})
con = sqlite3 . connect ( 'ssdse_b.db' )
_tbl . to_sql ( 'ssdse_b' , con , if_exists = 'replace' , index = False )
import matplotlib.pyplot as plt
con = sqlite3 . connect ( 'ssdse_b.db' )
df = pd . read_sql_query ( """
SELECT pref_name, population, births
FROM ssdse_b
WHERE year = 2020
ORDER BY population DESC
""" , con )
print ( df . head ())
print ( 'shape =' , df . shape )
📤 実行例 (上位 5 都道府県):
pref_name population births
0 東京都 14047594 99661
1 神奈川県 9237337 60865
2 大阪府 8837685 61878
3 愛知県 7542415 55613
4 埼玉県 7344765 47328
shape = (47, 3)
💬 結果の読み方 : 東京都が他を引き離す巨大な外れ値。 出生数は人口にほぼ比例するが、 大阪府 (61,878) は神奈川県 (60,865) より人口が少ないのに出生数は多いなど、 順位が入れ替わる県もある。 SQL は集計に強いが、 可視化は Python 側 に任せる発想がよい。 SQL ⇒ DataFrame ⇒ matplotlib の流れがデータ分析の定石。
図 4: SQL で抽出した 2020 年データの散布図 (人口 vs 出生数)。 右上の点が東京都。
3. GROUP BY で年次推移を集計
このコードでやること : GROUP BY year で年ごとに全国合計人口を集計し、 12 年間の推移を取得する。 SUM, AVG, COUNT は SQL の基礎集計関数で、 BI ツールが内部で自動生成する SQL でも頻出する。
📋 コピー SELECT
year ,
SUM ( population ) AS total_population ,
ROUND ( AVG ( population ), 0 ) AS avg_population_per_pref ,
COUNT ( DISTINCT pref_code ) AS n_prefectures
FROM ssdse_b
GROUP BY year
ORDER BY year ;
📤 実行例 (抜粋):
year total_population avg_population_per_pref n_prefectures
2012 127589000 2714660 47
2015 127094745 2704144 47
2020 126146099 2683960 47
2023 124353000 2645809 47
💬 結果の読み方 : 2012 年から 2023 年まで全国の総人口は一貫して減少しており、 12 年間で約 324 万人減った。 SQL の GROUP BY 1 行で「全国の人口減少トレンド」を客観的に定量化できる、 これが SQL を学ぶ価値。
図 5: SQL で抽出した 2020 年都道府県人口のヒストグラム。 右に長い裾 (東京都・神奈川県) を持つ偏った分布。
4. JOIN — 自己結合で対前年比を求める
このコードでやること : 同じ ssdse_b テーブルを 自己結合 (self join) し、 ある年とその前年の人口を横並びにして対前年比 (%) を計算する。 「JOIN は別テーブル同士を結ぶもの」と思いがちだが、 同一テーブル同士の結合も極めて実用的。
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12
13
14 SELECT
a . year AS year ,
a . pref_name AS pref ,
a . population AS pop_this ,
b . population AS pop_prev ,
ROUND ( 100.0 * ( a . population - b . population ) / b . population , 2 )
AS yoy_pct
FROM ssdse_b a
JOIN ssdse_b b
ON a . pref_code = b . pref_code
AND a . year = b . year + 1
WHERE a . year = 2020
ORDER BY yoy_pct DESC
LIMIT 5 ;
📤 実行例 (2020 年の人口対前年比トップ 5):
year pref pop_this pop_prev yoy_pct
2020 沖縄県 1467480 1462000 0.37
2020 東京都 14047594 14007000 0.29
2020 神奈川県 9237337 9224000 0.14
2020 埼玉県 7344765 7342000 0.04
2020 福岡県 5135214 5134000 0.02
💬 結果の読み方 : 2020 年時点でも沖縄・東京・神奈川が人口を増やしている。 一方、 同じクエリで ORDER BY yoy_pct ASC にすれば人口減少が大きい都道府県 (秋田・青森・岩手など) が浮かび上がる。 SQL の JOIN を覚えると「前年比」「移動平均」「累積和」などの時系列分析が一気に開ける。
5. ウィンドウ関数 — 都道府県内の順位付け
このコードでやること : 各年内で人口の多い順に順位を付ける。 RANK() OVER (PARTITION BY year ORDER BY population DESC) というウィンドウ関数 を使うと、 GROUP BY と違って「行を潰さずに集計値を併記」できる。
📋 コピー SELECT
year ,
pref_name ,
population ,
RANK () OVER ( PARTITION BY year ORDER BY population DESC ) AS rank_pop ,
PERCENT_RANK () OVER ( PARTITION BY year ORDER BY population DESC ) AS pct_rank ,
AVG ( population ) OVER ( PARTITION BY year ) AS avg_pop_year
FROM ssdse_b
WHERE year = 2020
ORDER BY rank_pop
LIMIT 5 ;
📤 実行例:
year pref_name population rank_pop pct_rank avg_pop_year
2020 東京都 14047594 1 0.000 2683960
2020 神奈川県 9237337 2 0.022 2683960
2020 大阪府 8837685 3 0.043 2683960
2020 愛知県 7542415 4 0.065 2683960
2020 埼玉県 7344765 5 0.087 2683960
💬 結果の読み方 : 上位 5 都道府県だけで全国合計人口の半分弱を占めることが、 population / avg_pop_year の比でも見える。 ウィンドウ関数は GROUP BY の上位互換と言われるくらい強力で、 BigQuery・Snowflake・Postgres などモダンな DBMS では必須スキル。
6. CTE と HAVING — 「平均より人口が多い県だけ」を抽出
このコードでやること : WITH 句 (CTE = Common Table Expression) で年ごとの平均人口を事前計算し、 そのうえで「平均超え」の都道府県を年度別に列挙する。 SQL を「ネストした副問合せ地獄」にしないコツが WITH 句である。
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 WITH year_avg AS (
SELECT year , AVG ( population ) AS mean_pop
FROM ssdse_b
GROUP BY year
)
SELECT
b . year ,
b . pref_name ,
b . population ,
ROUND ( b . population - y . mean_pop , 0 ) AS diff_from_mean
FROM ssdse_b b
JOIN year_avg y USING ( year )
WHERE b . population > y . mean_pop
AND b . year IN ( 2012 , 2020 )
ORDER BY b . year , diff_from_mean DESC ;
📤 実行例 (抜粋):
year pref_name population diff_from_mean
2012 東京都 13234000 10519340
2012 神奈川県 9070000 6355340
2020 東京都 14047594 11363634
2020 神奈川県 9237337 6553377
2020 大阪府 8837685 6153725
💬 結果の読み方 : 2012 年と 2020 年を比較すると、 平均を超える都道府県の顔ぶれはほぼ変わらない一方で、 「平均との差」が広がっている県が多い (東京は +10.5 百万 → +11.4 百万)。 これは人口集中の進行 を SQL 一発で可視化したことになる。 CTE は再利用しやすく、 大規模クエリの可読性を保つ上で必須。
図 6: 2012 年 vs 2020 年の都道府県人口分布 (箱ひげ図)。 中央値は減少、 上位の外れ値 (東京都) は逆に増大している。
7. SQL クエリ性能の見方 — EXPLAIN QUERY PLAN
このコードでやること : クエリプランを表示し、 インデックスが効いているかを確認する。 100 万行を超えるテーブルでは、 適切なインデックスがあるかないかで実行時間が 1000 倍 違うことも珍しくない。
📋 コピー EXPLAIN QUERY PLAN
SELECT pref_name , population
FROM ssdse_b
WHERE year = 2020 AND pref_code = 'R13000' ;
📤 実行例 (SQLite):
id parent notused detail
3 0 0 SEARCH ssdse_b USING INDEX idx_ssdse_b_pk (year=? AND pref_code=?)
💬 結果の読み方 : SEARCH ... USING INDEX と表示されていれば、 主キー索引が使われている (= 高速)。 これが SCAN ssdse_b だと全行スキャンで遅い。 SQL チューニングの第一歩は必ずプランを見ること 。
📊 SQL の方言比較 (実務頻出 5 系統)
「SQL」と一口に言っても、 実装ごとに細かな差がある。 SSDSE のような小規模分析なら SQLite / DuckDB、 業務システムなら PostgreSQL / MySQL、 ペタバイト級の分析基盤なら BigQuery / Snowflake が使われる。 主要な違いをまとめた。
機能
SQLite
PostgreSQL
MySQL
BigQuery
DuckDB
CTE (WITH)
○
○
○ (8.0+)
○
○
ウィンドウ関数
○ (3.25+)
○
○ (8.0+)
◎ (拡張多数)
○
JSON 関数
△
◎ (JSONB)
○
◎
○
配列・構造体
×
○
×
◎ ARRAY/STRUCT
○
並列実行
×
○
○
◎ クラスタ規模
◎ 1 マシン内
CSV/Parquet 直読み
△ 拡張
△ FDW
×
◎ 外部表
◎ ネイティブ
推奨ユースケース
アプリ内蔵・学習
業務 OLTP/分析
Web 系 OLTP
DWH/ペタ級分析
単機分析・CSV 連携
💡 SSDSE-B のような数万行〜数十万行 の教育用データなら、 DuckDB が一押し。 pip install duckdb だけで pandas と完全に統合され、 CSV をそのまま SELECT * FROM 'data/raw/SSDSE-B-2026.csv' で読める。 学生が SQL を体感する最短経路と言ってよい。
⚠️ SSDSE で SQL を扱う際に陥りやすい罠 (拡張版)
都道府県コードの型を間違える : R01000 は文字列、 '01000' の数値変換時に頭の 0 が消える事故が多発。 必ず TEXT 型のまま扱う。
年度の解釈ずれ : SSDSE-B は暦年 、 統計表によっては会計年度 。 比較する前に「Year は何年か」をメタデータで必ず確認すること。
NULL の集計 : SUM(NULL) = NULL、 COUNT(NULL) = 0。 直感に反する挙動でレポート数値が壊れる原因第一位。 COALESCE(col, 0) で明示的に置換するのが安全。
除算で 0 除算が混ざる : SUM(x)/SUM(y) で SUM(y)=0 だとエラーまたは NULL。 NULLIF(SUM(y), 0) を入れる癖をつける。
文字コード問題 : SSDSE は UTF-8 だが、 Windows の Excel で再保存すると Shift-JIS や BOM 付き UTF-8 になり、 SQLite の .import が文字化けする。 PowerShell なら iconv 相当の処理を挟む。
JOIN キーの欠落 : pref_code が R13000 と R13100 (区別あり) のように、 似て非なるキーを誤って結合すると行が爆発 (デカルト積) する。 SELECT COUNT(*) で件数を必ず検算。
🧪 自分で試す: 練習問題 6 題
以下のクエリを SSDSE-B-2026 を読み込んだ DB で実行してみよう。 答え合わせは Python (pd.read_csv → df.groupby) と比べると、 SQL の表現力が一段わかる。
2020 年の人口下位 5 都道府県を ORDER BY ... LIMIT で抽出せよ。
2003 年と 2020 年で人口減少率が最も大きい 5 県を、 自己結合 + ORDER BY yoy_pct ASC で求めよ。
各都道府県の出生率 (人口千対) (1000.0 * births / population) を年別に出し、 2020 年の上位 5 県を表示せよ。
ウィンドウ関数 LAG() を使い、 前年からの人口差分を新列として出せ。
CTE を 2 段重ねて「都道府県別の 10 年移動平均」と「全国平均」を併記する表を作れ。
EXPLAIN QUERY PLAN で、 WHERE pref_name = '東京都' はインデックスが効くか?効かない場合、 どんな索引を追加すべきか。
📚 SQL を独習するためのおすすめ教材
『達人に学ぶ SQL 徹底指南書 第 2 版』 (ミック 著、 翔泳社) — 集合論的な SQL 思考を養う名著。 GROUP BY の使い方が変わる。
『SQL ゼロからはじめるデータベース操作』 (ミック 著) — 初学者から中級まで橋渡し。 標準 SQL ベースで学べる。
SQLBolt (sqlbolt.com ) — ブラウザ上でクイズ形式に SQL を打てる無料サイト。
Mode Analytics SQL Tutorial — 分析用 SQL に特化、 ウィンドウ関数まで丁寧。
DuckDB 公式ドキュメント — モダン SQL の現在地が分かる。 SSDSE のような CSV データ分析に最適。
💡 学習ロードマップの提案 : ① SQLite で SSDSE-B を読む → ② SELECT/WHERE/ORDER BY を 1 週間徹底 → ③ GROUP BY と集計関数を 1 週間 → ④ JOIN と CTE を 2 週間 → ⑤ ウィンドウ関数で「対前年比」「ランキング」を書けるようになる、 という順で進めれば、 1〜2 か月で実務に通用する SQL 力が身に付く。 SSDSE は教師付きでない「自由研究」素材なので、 自分で問いを立てる練習にも最適。
🧠 SQL の根底を支える集合論的発想
SQL を「データを 1 行 1 行取り出して処理する命令」だと誤解していると、 すぐに行き詰まる。 SQL は本来「集合 (set) に対する変換 」を宣言する言語であり、 アルゴリズムを自分で組むのではなく、 「どんな集合がほしいか」 だけを書く。 SQL を学ぶうえで最も価値のある転換は、 この「命令型 (how) → 宣言型 (what) 」のスタイル変更である。
たとえば、 SSDSE-B-2026 で「2020 年の人口が全国平均を超える都道府県数」を求める場合、 Python で書けば「行をループして条件カウント」となる。 一方、 SQL では WHERE population > (SELECT AVG(population) FROM ssdse_b WHERE year = 2020) と書く。 ループも変数も無く、 結果集合の定義 だけを記述する。 この簡潔さこそ SQL が 50 年にわたって生き残ってきた理由だ。
関係代数 (relational algebra) と SQL の対応
関係代数の演算
記号
対応する SQL
SSDSE 上での例
選択 (selection) σ WHERE 2020 年だけ抜き出す
射影 (projection) π SELECT 列 人口列だけ取り出す
結合 (join) ⋈ JOIN ON 人口と出生数を同じ年度で対応付ける
和 (union) ∪ UNION 2010 年集合と 2020 年集合の合併
差 (difference) − EXCEPT 2010 年に存在し 2020 年に無い都道府県
直積 (Cartesian product) × FROM A, B (条件なし) 想定外: 行が爆発するので注意
グループ化 (grouping) γ GROUP BY 年ごとに人口合計を求める
重要なのは、 SQL を書くたびに「今、 集合のどの演算をしているか 」を意識すること。 そうすれば「JOIN を 5 段重ねたら結果が想定の 1000 倍になった」という事故を防げる。 関係代数を頭の片隅に置くだけで、 SQL は格段に読み書きしやすくなる。
🔄 SQL の実行順序 — 「書く順 ≠ 評価順」
SQL のもう一つの落とし穴は論理的な評価順 が、 書く順と異なることである。 たとえば SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... と書くが、 SQL エンジンはおおよそ次の順序で評価する。
FROM / JOIN — 対象テーブルを揃え、 結合した中間集合を作る
WHERE — 行レベルで絞り込み
GROUP BY — 残った行をキーごとに束ねる
HAVING — グループ単位で絞り込み (例: SUM > 100)
SELECT — 列を選択 / 集計関数を計算
DISTINCT — 重複を除去
ORDER BY — 並び替え
LIMIT / OFFSET — 先頭から N 件に切り詰める
この順序を覚えると、 「SELECT で付けた別名 AS yoy_pct は WHERE では使えないのに、 ORDER BY では使える」のような不思議な挙動が腹落ちする。 SELECT の評価は WHERE より後だが ORDER BY より前だからだ。
🧮 NULL の三値論理 — 「不明」を扱う SQL の流儀
SQL は通常の真偽値 (TRUE / FALSE) に加え、 UNKNOWN の 3 値を扱う。 これが NULL の比較で挙動が直感に反する原因。 SSDSE-B でも、 古い年度の一部列は NULL が混ざることがあり、 そのたびに集計値が大きくズレる。
式 結果 補足
NULL = NULLUNKNOWN TRUE にならない
NULL IS NULLTRUE IS / IS NOT を使う
NULL + 5NULL どんな演算でも伝染
SUM(NULL列)NULL を無視して合計 非 NULL のみ加算
COUNT(*)NULL も含めて行数 COUNT(列) は非 NULL のみ
AVG(NULL列)非 NULL の平均 分母は非 NULL の件数
NULL を扱う実用テクニック: ① COALESCE(col, 0) で代替値、 ② NULLIF(a, b) で 0 除算回避、 ③ WHERE col IS DISTINCT FROM v で NULL を含めた不一致比較 (PostgreSQL/標準 SQL)、 ④ レポート集計時は「NULL を含む / 含まない」を常に明示する。
🚀 SQL を「速く」するための実務知識
教育用 SSDSE-B (約 1000 行) では実行速度はほぼ無視できるが、 業務データ (1 億行以上) になるとクエリの書き方一つで 1 秒 vs 1 時間 の差が生まれる。 押さえるべき原則は次の通り。
不要な列を取らない : SELECT * ではなく必要な列だけ列挙する。 カラムストア DBMS (BigQuery, DuckDB, Snowflake) では特に効果が大きい。
WHERE で早く絞る : JOIN の前に絞れる条件は WHERE 句で絞る。 結合する行数を減らすのが最大の高速化。
適切なインデックスを張る : WHERE や JOIN ON で使う列にインデックスを。 過剰なインデックスは更新を遅くするので「読みやすさと書きやすさのトレードオフ」を見極める。
サブクエリを書きすぎない : CTE (WITH) や一時テーブルに切り出し、 オプティマイザを助ける。
EXPLAIN を読む癖 : 「全表スキャンか」「インデックス利用か」「行数推定が外れていないか」を必ず確認する。 SQLite なら EXPLAIN QUERY PLAN、 Postgres なら EXPLAIN (ANALYZE, BUFFERS)。
関数で列をくるまない : WHERE DATE(created_at) = '2026-05-30' はインデックスが効かない。 WHERE created_at >= '2026-05-30' AND created_at < '2026-05-31' と書き換える。
集計はストレージ近くで : BigQuery など分散 DB では「集計してから Python に取り出す」と通信量が大幅に減る。 100 万行を取り出して Python で集計するのは最悪パターン。
🛡 SQL とセキュリティ — SQL インジェクションを防ぐ
Web アプリで SQL を扱う際、 最大の事故源は SQL インジェクション 。 ユーザー入力を文字列連結でクエリに埋め込むと、 攻撃者が '; DROP TABLE users; -- のような断片を送り込んでテーブルを破壊できる。 防ぐ方法は単純で「必ずプレースホルダ (パラメータバインド) を使う 」こと。
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行)
都道府県 A1101(総人口)
北海道 5,092,000
東京都 14,086,000
沖縄県 1,468,000
…(全 47 行)
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 # ── この抜粋で使うデータベースを用意します ──
import sqlite3
import pandas as pd
con = sqlite3 . connect ( ':memory:' )
_b = pd . read_csv ( 'data/raw/SSDSE-B-2026.csv' , encoding = 'cp932' , skiprows = [ 1 ])
_b . rename ( columns = { 'SSDSE-B-2026' : 'year' , 'Prefecture' : 'pref_name' ,
'A1101' : 'population' })[[ 'year' , 'pref_name' , 'population' ]] \
. to_sql ( 'ssdse_b' , con , index = False , if_exists = 'replace' )
cur = con . cursor ()
cur . execute ( 'CREATE TABLE users (name TEXT)' )
cur . execute ( "INSERT INTO users VALUES ('taro')" )
user_input = 'taro'
# 危険な書き方 (絶対 NG)
cur . execute ( f "SELECT * FROM users WHERE name = ' { user_input } '" )
# 安全な書き方 (プレースホルダ)
cur . execute ( "SELECT * FROM users WHERE name = ?" , ( user_input ,))
# SSDSE 上での例: 都道府県名を安全にバインド
pref = '東京都' # 本来は input('都道府県名: ') で受け取る
df = pd . read_sql_query (
"SELECT year, population FROM ssdse_b WHERE pref_name = ? ORDER BY year" ,
con ,
params = ( pref ,)
)
print ( df . head ())
📤 実行例(実測)
year population
0 2012 13234000
1 2013 13307000
2 2014 13399000
3 2015 13515271
4 2016 13646000
💬 結果の読み方 : プレースホルダ ? (または %s, :name) を使うと、 ドライバが自動でエスケープしてくれる。 これだけで SQL インジェクションのほぼ 100% が防げる。 「文字列連結で SQL を組み立てない」を絶対のルールに。
📜 SQL の歴史と標準化
SQL は 1970 年に E. F. Codd が提唱した関係モデル理論を実装するため、 1974 年に IBM の Donald Chamberlin らが SEQUEL という名前で発表したのが起源。 その後 1986 年に ANSI/ISO が SQL-86 として初めて標準化し、 SQL-92, SQL:1999, SQL:2003 (ウィンドウ関数), SQL:2011 (時間表), SQL:2016 (JSON, ポリモーフィック表関数), SQL:2023 (グラフクエリ) と進化を続けている。
50 年以上にわたり、 ハードウェア (磁気テープ → SSD → クラウド) もデータ規模 (MB → PB) も劇的に変わったが、 SQL の中核構文 SELECT ... FROM ... WHERE ... はほぼ不変。 これは「集合論的に問いを書く」というアイデアが本質的に正しかったことの証拠。 SQL を学ぶ価値は陳腐化しない のである。
🧩 SQL と pandas の使い分け — 「どっちで書くか」の指針
SSDSE-B-2026 を分析する際、 同じ集計が SQL でも pandas でも書ける場面が多い。 どちらを選ぶかは「データの場所」と「再現性」で決めるのが実用的。 以下に対応関係をまとめた。
操作
SQL
pandas
使い分けの指針
行のフィルタ
WHERE year = 2020
df[df.year == 2020]
大規模なら SQL でストレージ側で絞る
列の選択
SELECT a, b
df[['a','b']]
通信量削減のため SQL 側で射影が原則
集計
GROUP BY year
df.groupby('year').sum()
どちらでも可、 行数で決める
結合
JOIN ON
df.merge(other, on='key')
両テーブルが DB 内なら SQL
ウィンドウ関数
RANK() OVER (...)
df.rank()
複雑な partition は SQL が読みやすい
時系列補間
標準 SQL では難
df.interpolate()
pandas / R が得意
統計検定
不可
scipy.stats
必ず Python 側で
機械学習
不可
sklearn
SQL は前処理まで
実務で推奨されるパターンは「SQL で必要な集計を済ませ、 結果セットを pandas に渡して可視化・統計検定・機械学習を行う 」というハイブリッド型。 SSDSE-B 程度のサイズなら全部 pandas でも問題ないが、 DB に蓄積された業務データを扱う場面では「SQL で前処理を集約する」のが鉄則である。
🔍 SSDSE-B から派生する分析テーマ集
SQL の練習用に SSDSE-B-2026 から取り組める分析テーマを 10 個挙げる。 どれもクエリ 1〜2 段で結論が出るので、 1 日 1 テーマのペースで進めると 2 週間で SQL に慣れる。
人口減少の最速県 — 自己結合で 5 年前比 / 10 年前比を出し、 ランキングする。
出生率 (出生数 A4101 / 総人口 A1101) — 算出列を作り、 上位下位を比較。 沖縄と東北の格差が一目で見える。
東京一極集中の進行 — 各年で「全国人口に占める東京都の比率」を時系列に出す。
高齢化率の県別変化 — 65 歳以上人口 / 総人口を計算し、 増加幅トップ 5 を抽出。
出生率 vs 平均年収 — GROUP BY なしの単純 JOIN で相関を見る。 通説と一致するか?
失業率の時系列 — 年・県・失業率の 3 列で RANK() し、 最悪期を特定。
消費支出の費目構成 — 教育費 (L322108) / 消費支出 (L3221) の比で教育支出の厚い県を抜き出す。
高齢化率 vs 出生率 — 65 歳以上人口 (A1303) / 総人口と出生率 (A4101/A1101) を JOIN で並べ、 相関係数を出す。
大学進学率の地域差 — 各県の進学率を年代別に比較、 都市集中度を測る。
外国人人口比率の急増県 — 自己結合で 10 年前比を取り、 増加トップ 5。 沖縄・北海道などの観光県が現れるか確認。
これらすべてに共通するパターンは「絞り込み (WHERE) → 集計 (GROUP BY / 自己 JOIN) → 順位付け (ORDER BY) 」の三段構え。 SQL の 7 割はこのパターンの応用で書ける。 SSDSE-B のような小さな実データで「定型パターンを身体に染み込ませる」のが最短の上達法である。
📦 SQL の主要オブジェクト — 表・ビュー・インデックス・トランザクション
SQL を使いこなすためには、 単に SELECT を書くだけでなく、 データベース内に存在する主要オブジェクト とそれらが果たす役割を理解しておく必要がある。 SSDSE-B-2026 を題材にしても、 これらの概念は早晩必ず登場するため、 ここで概観しておく。
テーブル (table) : 行と列から成る基本単位。 「実体のデータ」をここに格納する。 SSDSE-B の ssdse_b はテーブルそのもの。
ビュー (view) : 「保存されたクエリ」。 物理データは持たず、 アクセスのたびに再計算される。 たとえば CREATE VIEW v_2020 AS SELECT * FROM ssdse_b WHERE year = 2020 とすれば、 以後 SELECT * FROM v_2020 で 2020 年分だけを取り出せる。 アクセス権限の集約や複雑クエリの隠蔽に使う。
マテリアライズド・ビュー : ビューを物理化したもの。 集計結果を保持しておき、 元データ更新時のみ再計算。 BigQuery や PostgreSQL では大規模ダッシュボードの高速化で必須。
インデックス (index) : 検索高速化のための索引。 紙の本の巻末索引と同じ仕組み。 B-Tree, Hash, GIN, BRIN など複数の種類があり、 用途に応じて使い分ける。 過剰に張ると INSERT/UPDATE が遅くなる副作用に注意。
トランザクション (transaction) : 複数の SQL 文を「全部成功するか、 全部やり直す」単位として束ねる仕組み。 BEGIN; ... COMMIT; もしくは ROLLBACK; で制御する。 ACID 特性 (原子性・一貫性・独立性・持続性) を保証する根幹。
制約 (constraint) : PRIMARY KEY, FOREIGN KEY, UNIQUE, NOT NULL, CHECK など、 データ品質を SQL レイヤーで強制する仕組み。 アプリケーションのバグからデータを守る最後の砦。
ストアド・プロシージャ / 関数 : SQL に手続き的な処理を埋め込む。 トリガー (TRIGGER) と組み合わせて、 監査ログや自動更新を実装する。 ただし可搬性は下がるため、 アプリ側で書くか DB 側で書くかは設計判断。
教育用途では「テーブル + インデックス + ビュー + トランザクション」の 4 つを優先的に押さえれば十分。 SSDSE-B 上で CREATE VIEW や BEGIN TRANSACTION を実際に試してみると、 「DB は単なるファイルではなく、 業務ロジックを宿す入れ物だ」という感覚が掴める。
ビュー作成の実例 (SSDSE-B)
このコードでやること : SSDSE-B から「出生率 (人口千対)」を計算するビューを作成し、 以後の分析で繰り返し利用する。 同じ計算を毎回書くのではなく、 ビューに名前を付けて再利用するのが SQL の良作法。
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 CREATE VIEW v_birth_rate AS
SELECT
year ,
pref_code ,
pref_name ,
population ,
births ,
ROUND ( 1000.0 * births / NULLIF ( population , 0 ), 3 ) AS birth_rate_permil
FROM ssdse_b ;
-- 2020 年で 出生率 ( 人口千対 ) の上位 5 県
SELECT pref_name , birth_rate_permil
FROM v_birth_rate
WHERE year = 2020
ORDER BY birth_rate_permil DESC
LIMIT 5 ;
📤 実行例:
pref_name birth_rate_permil
沖縄県 10.183
福岡県 7.588
熊本県 7.485
佐賀県 7.399
滋賀県 7.383
💬 結果の読み方 : 出生率 (人口千対) では沖縄が 10.2 と突出し、 続いて福岡・熊本・佐賀・滋賀など九州・西日本の県が上位に来る。 人口の多い大都市圏 (東京・大阪) は上位に入らず、 単純な人口順位とは全く違う序列が見える。 ビューに切り出しておけば、 別の年度・別の指標と組み合わせる時にクエリが格段に簡潔になる。
🧷 SQL 学習者がよく聞く Q&A (拡張)
Q1. NoSQL があれば SQL はもう不要では?
A1. いいえ。 大規模ログや非構造データに NoSQL は強いが、 「整合性が要求される取引」「複雑な集計」「BI ダッシュボード」では依然 SQL が事実上の標準。 さらに DynamoDB や MongoDB といった NoSQL も、 後付けで SQL ライクなクエリ言語 (PartiQL, MQL) を取り入れている。 SQL の学習価値は失われていない。
Q2. SQLite と PostgreSQL、 学習はどちらから?
A2. SQLite から 。 インストール不要、 1 ファイルで完結、 SSDSE-B のロードも秒単位。 慣れたら PostgreSQL に移って多人数同時アクセス・関数定義・外部キー制約を体験するのが王道。
Q3. クエリが遅いです。 まず何を見れば?
A3. (1) EXPLAIN でプラン確認、 (2) WHERE 列にインデックスがあるか、 (3) JOIN の片方が極端に大きくないか、 (4) SELECT * を必要な列だけに絞れないか。 この 4 つを順に見れば 8 割の遅延は解消する。
Q4. ORM (SQLAlchemy 等) を使えば SQL は知らなくていい?
A4. 表面的な CRUD だけなら ORM で済む。 しかし「N+1 問題」「複雑な JOIN」「ウィンドウ関数」「性能チューニング」「障害対応」は ORM の抽象を貫通して SQL の知識が必須となる。 ORM は SQL を知った上で 使うのが正しい。
Q5. SSDSE-B 以外におすすめのオープンデータは?
A5. e-Stat (政府統計の総合窓口) 、 RESAS (地域経済分析システム) 、 DATA.GO.JP 、 東京都オープンデータカタログ 、 国際的には World Bank Open Data など。 どれも CSV で配布されており、 SQLite に .import するだけで SQL の練習素材になる。
🎓 まとめ — SQL を学ぶ意義 (再確認)
SQL は関係モデル という強力な数学的土台と、 集合論的 な記述方法を持ち、 50 年以上の標準化と継続的進化により、 ペタバイト級のデータ分析からスマートフォン内蔵 DB (SQLite) まで広範に使われている。 SSDSE-B-2026 のような実在の公的データ を題材に SELECT/WHERE/GROUP BY/JOIN/CTE/ウィンドウ関数を順に体得すれば、 業務・研究・教育のどの場面でも通用するスキルになる。 Python (pandas) や BI ツール、 ETL ジョブの裏側にも必ず SQL があるため、 「データを扱うすべての人にとっての共通言語」と言って過言ではない。 関係モデル・集合論・NULL の三値論理・実行順序・性能チューニング・セキュリティ・歴史と標準化、 そして SSDSE-B での実践例まで一通り押さえれば、 SQL は単なる「DB を操作するための文法」から「データに対する問いの設計言語 」へと姿を変える。 ここまで来れば、 統計学・機械学習・データ可視化のどの分野に進んでも、 SQL は強力な味方であり続ける。
最後にもう一度強調しておきたいのは、 SQL の学習は手を動かす時間に比例する ということ。 本ページに登場した SSDSE-B-2026 のクエリは、 すべて自分の手で叩いて結果を確認してこそ身に付く。 おすすめの学習サイクルは「① 自分で問いを立てる (例: 出生率の上位 5 県は?) → ② 標準クエリパターンに当てはめる (絞り込み → 集計 → 順位付け) → ③ EXPLAIN で実行プランを確認 → ④ 結果を Python (pandas + matplotlib) で可視化 → ⑤ Notebook にまとめて再現性を担保する」の 5 ステップを繰り返すこと。 1 サイクルが 30 分以内で回せるようになると、 SQL は「文法を思い出す道具」から「思考の道具」へと完全に移行する。 これを 50 サイクル (= 25 時間) 繰り返した頃には、 標準的な業務クエリの 9 割は自力で書けるようになり、 残りの 1 割が「より深い SQL の世界 (ストアド・プロシージャ、 並列処理、 OLAP キューブなど)」の入口になる。 SSDSE-B-2026 のような豊かな公的データは、 こうしたサイクルを回すための無料で良質な練習場 と言える。
本ページで触れた内容を 1 行で要約 するなら、 「SQL は関係モデルに基づく宣言的データ操作言語 であり、 SSDSE-B-2026 のような実データを題材に絞り込み・集計・結合・ウィンドウ関数を順に体得することで、 業務でも研究でも教育でも長期にわたり使い続けられる中核スキルになる」。 この一言を腑に落とした上で、 ぜひ自分の手で SQLite を立ち上げ、 SSDSE-B の CSV を .import し、 1 つ目のクエリ SELECT COUNT(*) FROM ssdse_b; を実行してみてほしい。 そこから先は、 問いの数だけ SQL が伸びていく。 学びはじめのハードルが低く、 上達の天井が高い — これが SQL の最大の魅力である。
さらに付け加えるなら、 SQL を 1 年触り続けると「自分が扱えるデータ規模」 が桁違いに広がる。 Excel では 100 万行で限界が来るが、 SQLite なら 1 億行、 PostgreSQL なら数十億行、 BigQuery なら数兆行が現実的な射程に入る。 つまり SQL を覚えることは「分析可能なデータ規模を 10000 倍にする 」ことに等しい。 SSDSE-B-2026 で養った基礎を足がかりに、 e-Stat の大規模調査票、 政府オープンデータ、 企業のログ・売上データへと対象を広げていけば、 「データから事実を引き出す力」は確実に積み上がっていく。 SQL を学ぶことは、 未来の自分への最良の投資の一つ であり、 知識経済の時代に必須の素養と言える。 SSDSE-B-2026 という身近な公的データから一歩を踏み出そう。
🧮 数式に値を入れて手で計算する: SQL 結合と集計
合成 2 テーブル INNER JOIN + GROUP BY の結果行数を計算する。
Step 1: テーブル
customers: 100 行
orders: 500 行 (顧客毎 1-10 件)
JOIN 後: 500 行
Step 2: 集計
GROUP BY customer_id: 100 グループ
COUNT(orders) per customer: 平均 5
🐍 Python で再現
📋 コピー customers = 100
orders = 500
join_rows = orders
groups = customers
avg_per_customer = orders / customers
print ( f "JOIN 後: { join_rows } " )
print ( f "GROUP 数: { groups } " )
print ( f "平均注文数: { avg_per_customer } " )
📤 実行結果
JOIN 後: 500
GROUP 数: 100
平均注文数: 5.0
💬 手計算 (Step 2) と Python 出力が完全一致。
🐍 Python 実装
SSDSE-B-2026 (47 都道府県 × 112 列) を SQLite のオンメモリ DB に投入し、 出生数上位 5 県を ORDER BY 出生数 DESC LIMIT 5 で取り出す最小例。 skiprows=1 で SSDSE 特有の英語ヘッダ行を読み飛ばすのが定番。
📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年)
年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) …
2023 R01000 北海道 5,092,000 1,681,000 24,430 …
2023 R13000 東京都 14,086,000 3,205,000 86,348 …
2023 R47000 沖縄県 1,468,000 350,000 12,549 …
…(残り 112 列は住宅・家計・教育・医療など)
📋 コピー import pandas as pd , sqlite3
df = pd . read_csv ( 'data/raw/SSDSE-B-2026.csv' , skiprows = 1 , encoding = 'cp932' )
conn = sqlite3 . connect ( ':memory:' )
df . to_sql ( 'pref' , conn , index = False )
query = 'SELECT 都道府県, 出生数 FROM pref ORDER BY 出生数 DESC LIMIT 5'
print ( pd . read_sql ( query , conn ))
📤 実行例(実測)
都道府県 出生数
0 東京都 113194
1 東京都 111964
2 東京都 110629
3 東京都 109986
4 東京都 108990
⚠️ よくある落とし穴
SQL の落とし穴は「NULL の挙動」「JOIN の組合せ爆発」「方言の違い」の三大要因に集約されます。 SSDSE データを JOIN する際にも、 県コードの型 (文字列 vs 数値) のズレで予想外の行数になりやすい。
❌ SELECT * の濫用
本番では必要列だけ指定。 ネットワーク帯域・コストに直結。
❌ JOIN の組み合わせ爆発
誤った JOIN で行数が掛け算的に膨らむ。 中間結果を都度確認。
❌ NULL の比較ミス
= NULL ではなく IS NULL。 集計関数も NULL を無視する点に注意。
❌ 方言の違い
MySQL・PostgreSQL・BigQuery で関数名が異なる。 移植時に注意。
⚠️ 追加の落とし穴(実務編)
❌ NULL の扱いを忘れる
WHERE col = NULL は常に偽。 正しくは WHERE col IS NULL。 集約関数も NULL を無視するので件数の解釈に注意。
❌ SQL インジェクション
ユーザ入力を文字列連結で SQL に埋め込むのは致命的脆弱性。 必ずプレースホルダ(? や :name)を使う。
❌ インデックス未活用
大規模テーブルで WHERE の列にインデックスがないとフルスキャン。 EXPLAIN で実行計画を確認。
❌ 暗黙の型変換
文字列と数値を比較すると、 RDBMS によっては予期せぬ結果。 CAST で明示的に変換。
❌ トランザクション忘れ
UPDATE / DELETE は BEGIN〜COMMIT で囲むのが安全。 失敗時に ROLLBACK。
❌ サブクエリの濫用
深いネストは可読性ゼロ。 CTE(WITH 句)でフラット化する。
⚙️ 上級パターン集
パターン 1:CTE(共通テーブル式)
WITH ranked AS (SELECT ..., RANK() OVER ...) SELECT * FROM ranked WHERE rank <= 5。 中間結果に名前を付けて再利用、 可読性向上。
パターン 2:Window 関数
RANK / DENSE_RANK / ROW_NUMBER / LAG / LEAD / SUM() OVER (...)。 集約しつつ全行を保持できる。 SSDSE-B で「人口の前県差」「累積出生数」を即座に計算可能。
パターン 3:再帰 CTE
階層データ(組織図・地域階層)を WITH RECURSIVE で展開。 「親 → 子 → 孫」の連鎖を 1 クエリで取れる。
パターン 4:PIVOT / UNPIVOT
長形式 ↔ 横形式の変換。 標準 SQL には PIVOT がないが、 CASE + GROUP BY で代用可能。 SSDSE-B-2026 の年齢区分を横展開して可視化用に整形。
パターン 5:JSON 操作
PostgreSQL の jsonb 型、 MySQL の JSON_EXTRACT。 半構造データを SQL で扱える。 API レスポンスのログ分析で頻出。
パターン 6:UPSERT
INSERT ... ON CONFLICT DO UPDATE(PostgreSQL)、 INSERT ... ON DUPLICATE KEY UPDATE(MySQL)。 「あれば更新、 なければ挿入」を 1 文で。
🔗 JOIN の完全ガイド
SQL で最も誤解されやすいのが JOIN です。 5 種類の JOIN を集合論的に整理します。
JOIN 種類
集合論的意味
SSDSE での例
INNER JOIN 両方に存在する行のみ(共通部分) SSDSE-B と SSDSE-C の共通コード県
LEFT JOIN 左テーブル全行 + 右の一致する行 SSDSE-B 全 47 県 + 補助データの該当行
RIGHT JOIN 右テーブル全行 + 左の一致 補助データ全行 + SSDSE-B の該当行
FULL OUTER JOIN 両方の全行(和集合) 2 つのデータソースのすべて
CROSS JOIN 直積(全組合せ) 47 県 × 12 年 = 564 行
JOIN 実装例
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行)
都道府県 A1101(総人口) A1303(65歳以上人口) L3221(消費支出(二人以上の世帯)) L322101(食料費(二人以上の世帯)) L322108(教育費(二人以上の世帯))
北海道 5,092,000 1,681,000 296,888 74,341 6,911
東京都 14,086,000 3,205,000 341,320 97,776 24,160
沖縄県 1,468,000 350,000 251,222 73,453 6,356
…(全 47 行)
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 # ── この抜粋で使うデータを用意します(SSDSE-B の 47 都道府県・最新年度)──
import pandas as pd
df = pd . read_csv ( 'data/raw/SSDSE-B-2026.csv' , encoding = 'cp932' , header = 1 )
df = df [ df [ '地域コード' ] . astype ( str ) . str . match ( r '^R\d {5} $' , na = False )] . copy ()
df [ '年度' ] = pd . to_numeric ( df [ '年度' ], errors = 'coerce' )
df = df [ df [ '年度' ] == df [ '年度' ] . max ()]
for _c in df . columns [ 3 :]:
df [ _c ] = pd . to_numeric ( df [ _c ], errors = 'coerce' )
df [ '高齢化率' ] = df [ '65歳以上人口' ] / df [ '総人口' ] * 100
df_b = df # この抜粋では df_b という名前で参照する
# 見本でよく使われる仮の列名を、実データから作っておく
df [ 'income' ] = df [ '消費支出(二人以上の世帯)' ]
df [ 'population' ] = df [ '総人口' ]
_region = { '北海道' : '北海道' , '青森県' : '東北' , '岩手県' : '東北' , '宮城県' : '東北' ,
'秋田県' : '東北' , '山形県' : '東北' , '福島県' : '東北' , '茨城県' : '関東' ,
'栃木県' : '関東' , '群馬県' : '関東' , '埼玉県' : '関東' , '千葉県' : '関東' ,
'東京都' : '関東' , '神奈川県' : '関東' }
df [ 'region' ] = df [ '都道府県' ] . map ( _region ) . fillna ( 'その他' )
df [ '地域' ] = df [ 'region' ]
import pandas as pd
import sqlite3
# SQL 側は Prefecture / A1101 / Code という英字の列名で書いているので、
# ここは skiprows=[1](英字の項目コードを見出しにする)で読み直す。
df_b = pd . read_csv ( 'data/raw/SSDSE-B-2026.csv' , encoding = 'cp932' , skiprows = [ 1 ])
df_b = df_b [ df_b [ 'SSDSE-B-2026' ] == 2023 ] # 2023 年の 47 都道府県
# df_c は「家計の費目だけを取り出した別表」。
# SSDSE-C は市区町村単位で都道府県コードとは結合できないので、
# 同じ SSDSE-B から家計の列だけを切り出して 2 つ目の表にする。
df_c = df_b [[ 'Code' , 'L3221' , 'L322101' , 'L322108' ]] . copy ()
df_b = df_b [[ 'Code' , 'Prefecture' , 'A1101' , 'A1303' ]] . copy ()
conn = sqlite3 . connect ( ':memory:' )
df_b . to_sql ( 'pref' , conn , index = False )
df_c . to_sql ( 'family' , conn , index = False )
q = """
SELECT p.Prefecture, p.A1101 AS 人口, f.*
FROM pref p
INNER JOIN family f ON p.Code = f.Code
LIMIT 5
"""
print ( pd . read_sql ( q , conn ))
📤 実行例(実測)
Prefecture 人口 Code L3221 L322101 L322108
0 北海道 5092000 R01000 296888 74341 6911
1 青森県 1184000 R02000 263371 77899 6713
2 岩手県 1163000 R03000 298536 81997 6748
3 宮城県 2264000 R04000 305541 83835 11245
4 秋田県 914000 R05000 272086 78124 4316
⚡ パフォーマンスチューニング
基本:EXPLAIN で実行計画を見る
クエリの前に EXPLAIN(または EXPLAIN ANALYZE)を付けると、 DB が選んだ実行計画が表示される。 「Seq Scan(全件走査)」が出ていればインデックス未活用のサイン。
インデックスの貼り方
WHERE・JOIN ON・ORDER BY で頻繁に使う列にインデックスを作成。 ただし更新コストとのトレードオフ。 SSDSE のような読み取り専用データならインデックス過多でも問題なし。
クエリ書き換えのテクニック
サブクエリ → JOIN への書き換え :多くの場合 JOIN の方が速い
OR → UNION への書き換え :インデックス活用度向上
NOT IN → NOT EXISTS への書き換え :NULL の扱いが正しくなる
COUNT(*) vs COUNT(col) :意味が違う、 速度も違う
大規模データでの戦術
パーティショニング :年・地域でテーブル分割
マテリアライズドビュー :集計結果を物理保存
列指向 DB :DuckDB / BigQuery / Redshift などへ移行
サンプリング :TABLESAMPLE BERNOULLI(1) で 1% サンプル
📜 SQL の歴史
1970 年 E. F. Codd の論文「A Relational Model of Data for Large Shared Data Banks」が出発点。 1974 年 IBM の Chamberlin と Boyce が SEQUEL(後の SQL)を実装。 1979 年に Oracle が世界初の商用 RDBMS を販売。 1986 年に ANSI が標準化(SQL-86)、 以降 SQL-92、 SQL:1999、 SQL:2003 と機能拡張。 2003 年 SQL:2003 で Window 関数が標準入り。 2016 年 SQL:2016 で JSON 操作が標準入り。 50 年以上経った今も、 「SQL は死んだ」と言われては「やっぱり生き残った」を繰り返しています。 NoSQL ブームの後にも「NewSQL」が登場し、 SQL は不滅です。
2010 年代以降は、 SQL を直接書くだけでなく、 ORM(Object-Relational Mapping)経由でアクセスするのも一般化しました。 しかし「ORM が遅い」「ORM の生成 SQL が読めない」という問題は今も健在で、 結局 SQL を読み書きできる人材の価値は下がっていません。 むしろデータが爆発する 2026 年現在、 SQL リテラシーは「全エンジニアの必須教養」と化しています。
📋 SQL チートシート
必須クエリ集
件数 :SELECT COUNT(*) FROM t
ユニーク値 :SELECT DISTINCT col FROM t
上位 N 件 :SELECT * FROM t ORDER BY col DESC LIMIT N
条件件数 :SELECT COUNT(CASE WHEN col > 0 THEN 1 END) FROM t
累計 :SELECT col, SUM(col) OVER (ORDER BY id) FROM t
順位 :RANK() OVER (ORDER BY col DESC)
n 番目 :ROW_NUMBER() OVER (...)
パーセンタイル :PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY col)
欠損補完 :COALESCE(col, default)
NULL チェック :WHERE col IS NULL
集約関数一覧
COUNT(*) 全件、 COUNT(col) NULL 以外
SUM, AVG, MIN, MAX 数値集計の基本
STDDEV, VARIANCE 標準偏差・分散
GROUP_CONCAT / STRING_AGG 文字列結合
FIRST_VALUE, LAST_VALUE Window 関数の終端
🔄 pandas ↔ SQL 対応表
操作
SQL
pandas
列選択 SELECT a, bdf[['a','b']]
行絞り込み WHERE a > 5df[df.a > 5]
集約 GROUP BY adf.groupby('a')
並べ替え ORDER BY a DESCdf.sort_values('a', ascending=False)
結合 JOIN ONdf1.merge(df2, on='key')
ユニーク DISTINCTdf.drop_duplicates()
上位 N LIMIT Ndf.head(N)
件数 COUNT(*)len(df)
ウィンドウ RANK() OVERdf.col.rank()
NULL 確認 IS NULLdf.col.isna()
🔗 関連用語 (前提・並列・発展)
この用語と直接結びつく前提・並列・発展用語。 学習順序の参考にどうぞ。
🔗 テーブル
SQL の操作対象。
🔗 BIツール
BI は裏で SQL を生成する。
🔗 外部結合
SQL の JOIN の重要バリアント。
🔗 Tidy data
SQL で扱いやすい表形式。
🔗 関連用語ナビ(拡張)
前提として知っておく
並列で比較するもの
発展として学ぶ
🎓 SQL の哲学:宣言型思考の力
SQL の本質は「何が欲しいかを書けば、 どう取るかは DB が考える」 という宣言型パラダイムにあります。 これは関数型プログラミングや論理プログラミング(Prolog)にも通じる思想で、 「What を書き、 How を任せる」ことで、 抽象度の高い記述を可能にします。
同じ問い合わせを書く 3 通りの方法 ── 手続き型(C 言語でループを書く)/関数型(map / reduce)/宣言型(SQL)── のうち、 宣言型は最も短く、 最適化の余地が最も大きい。 DB の最適化器は、 ハードウェア・データ分布・統計情報を踏まえて、 同じ SQL を異なる実行計画で実行できます。 これが、 50 年経っても SQL が生き残る理由です。
SQL を学ぶことは、 単に「DB を操作する文法を覚える」ことではなく、 「集合論的に思考する 」「宣言型で抽象化する 」というプログラミング哲学を身に付けることでもあります。 これは pandas・Spark・dplyr など、 他のデータ操作ライブラリでも繰り返し出現する思考様式なので、 SQL を 1 度マスターすると他言語への移行が容易になります。
🌟 モダン SQL の新機能
JSON / JSONB
PostgreSQL の JSONB 型は半構造データを高速に検索可能。 data->>'key' で抽出。 API ログ・ユーザ属性の格納で頻用。
GENERATED COLUMNS
他の列から計算される仮想列を定義。 SSDSE で「高齢化率 = A1303 / A1101」を仮想列として持てる。 SQL Server・PostgreSQL でサポート。
FULL TEXT SEARCH
tsvector(PostgreSQL)、 FULLTEXT インデックス(MySQL)。 全文検索を SQL の枠内で。
配列型
PostgreSQL の integer[] 等の配列型。 タグ・複数選択肢の格納に便利。 ANY(array) で要素検索。
PIVOT / UNPIVOT(標準化進行中)
SQL:2023 で標準化提案中。 SSDSE-B の年齢区分を横展開 → 縦展開に簡潔記述。
ベクトル検索(pgvector など)
LLM 埋め込みベクトルを SQL で検索。 RAG(検索拡張生成)の基盤として急速普及。 2024 年以降の必修トピック。
✅ クエリ提出前 最終チェック
□ SELECT * を使わず必要列のみ指定したか
□ WHERE 条件で型ミスマッチがないか
□ NULL の扱い(IS NULL)が正しいか
□ JOIN ON 条件を明示したか
□ GROUP BY に SELECT 列がすべて含まれるか
□ HAVING を WHERE と混同していないか
□ LIMIT の前に ORDER BY があるか
□ Window 関数の PARTITION BY が意図どおりか
□ サブクエリを CTE で分解したか
□ EXPLAIN で実行計画を確認したか
□ プレースホルダで SQL インジェクション対策をしたか
□ トランザクション境界を意識したか
□ 結果件数が期待値どおりか
□ コメントで意図を残したか
□ Git でクエリを版管理しているか
🎬 利用シナリオ詳述
シナリオ A:データ分析の朝の儀式
毎朝出社して最初にやるのが「昨日の KPI を SQL で集計」。 dbt や Airflow でパイプラインが自動実行されており、 結果テーブルに SELECT * FROM daily_kpi WHERE date = CURRENT_DATE - 1。 ダッシュボード化されていれば Slack に画像 push。
シナリオ B:研究室の修論データ
SSDSE のような CSV を Jupyter で pd.read_csv → df.to_sql でメモリ DB に。 探索的に SQL を発行して仮説検証。 結果が出たら pandas に戻して可視化。 修論で「データを SQL で前処理した」と書ける。
シナリオ C:データエンジニア面接
「上位 3 件の重複なし」「N 日連続ログイン」「2 番目に高い値」などの定番問題を SQL で書く面接。 Window 関数を使った優雅な解法を即興で書けるかが鍵。 LeetCode SQL Easy → Medium で訓練。
シナリオ D:本番障害対応
「クエリが急に遅くなった」と Slack 通知。 SHOW PROCESSLIST で実行中クエリを確認、 EXPLAIN ANALYZE で実行計画調査、 必要ならインデックス追加。 オンコールエンジニアの日常。
🔤 SQL のデータ型早見表
分類
型
用途
整数 INT, BIGINT, SMALLINT 人口、 件数、 ID
実数 DECIMAL, NUMERIC, FLOAT, DOUBLE 価格、 率、 統計量
文字列 VARCHAR(N), TEXT, CHAR(N) 県名、 説明文
日時 DATE, TIME, TIMESTAMP, INTERVAL 取得日、 タイムスタンプ
真偽 BOOLEAN フラグ
バイナリ BLOB, BYTEA 画像、 ファイル
JSON JSON, JSONB 半構造データ
配列 INTEGER[], TEXT[] タグ、 複数選択
地理空間 POINT, GEOMETRY(PostGIS) 緯度経度
ベクトル vector(pgvector) 埋め込みベクトル
📇 インデックス設計の指針
インデックスは「読み取り高速化」と「書き込みコスト増」のトレードオフ。 SSDSE のように 47 行・読み取り専用なら過剰、 数千万行・更新頻繁なら慎重設計が必要です。
インデックスを貼るべき列
主キー / ユニーク列 — 自動的にインデックスが作成される
WHERE で頻繁に使う列 — 等値検索・範囲検索の高速化
JOIN ON の列 — JOIN 性能の鍵
ORDER BY の列 — ソート不要に
外部キー — 整合性チェックと JOIN の両方で活用
インデックスの種類
B-Tree — 最も汎用、 等値・範囲両方
Hash — 等値検索のみ高速
GiST / GIN — PostGIS、 全文検索、 配列
BRIN — 巨大時系列テーブルに低コスト
部分インデックス — WHERE deleted = false でフィルタ済み
複合インデックス — 列順序が重要、 先頭列を WHERE で使うのが必須
インデックス確認の SQL
-- PostgreSQL
SELECT * FROM pg_indexes WHERE tablename = 'pref';
-- MySQL
SHOW INDEX FROM pref;
-- SQLite
SELECT * FROM sqlite_master WHERE type='index';
🔄 トランザクションと ACID 特性
RDBMS の信頼性は ACID 4 特性に集約されます。 これは銀行決済・在庫管理・予約システムなどで「絶対に壊れてはいけない」処理を支える基盤です。
Atomicity(原子性) :トランザクション内の処理は「全部成功」か「全部失敗」のどちらか
Consistency(一貫性) :制約を満たした状態から別の制約を満たす状態へ遷移
Isolation(独立性) :並行トランザクションが互いに影響しない
Durability(永続性) :COMMIT したらクラッシュしてもデータは残る
トランザクションの基本構文
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
-- 途中エラーなら ROLLBACK
COMMIT;
分離レベル
レベル
ダーティリード
ノンリピータブルリード
ファントムリード
READ UNCOMMITTED 起こる 起こる 起こる
READ COMMITTED 防ぐ 起こる 起こる
REPEATABLE READ 防ぐ 防ぐ 起こる
SERIALIZABLE 防ぐ 防ぐ 防ぐ
📚 関連グループ教材・さらに学ぶには
このサイト内
論文一覧に戻る — SQL を実際に使った再現論文をハンズオン形式で読む
関連用語ページ — このページの「🔗 関連用語」から派生
用語集トップ — 全用語を一覧で確認
概念マップ — 用語間の関係を視覚化
推奨書籍・教材
『統計学入門』 (東京大学出版会)― 日本語統計入門の定番。 データエンジニアリング の基礎が押さえられる。
『Pythonによるデータ分析入門』 (Wes McKinney、 O'Reilly)― pandas 作者による実装ガイド。
『機械学習のエッセンス』 (加藤公一、 SBクリエイティブ)― ML 基礎を Python で実装しながら学ぶ。
『因果推論の科学』 (Judea Pearl、 文藝春秋)― 相関と因果の違いを徹底解説。
オンライン教材
scikit-learn 公式ドキュメント — 機械学習の標準実装。
StatQuest (YouTube) — 統計概念を直感的に解説。
Coursera / edX — 体系的なオンライン講座。
SSDSE 公式 — 本サイトで使う公的データの提供元。
困ったときは
データの可視化 (散布図・ヒストグラム・箱ひげ図) で全体像を把握
サンプルサイズ・欠損・外れ値を確認
適用条件 (前提) が満たされているか診断
類似研究での標準的な手法を確認
結果を複数手法でクロスチェック
📜 歴史的背景と学習の位置づけ
SQL は データエンジニアリング の領域で発展してきた概念です。 ここでは大まかな歴史的背景と、 なぜこの概念が必要になったのかを整理します。 用語が「降ってきた」のではなく、 現実の問題を解くために順番に 編み出されたものだと知ると、 学習の納得感が違います。
なぜこの概念が生まれたか
データ分析や AI を実務で使うと、 「単純な数式」「直感だけのモデル」では太刀打ちできない場面が必ず出てきます。 SQL は、 そうした実務的な課題を整理し、 共通言語として定式化したものです。 そのため、 教科書だけで完結する話ではなく、 使う場面 と使わない場面 を見極めることが何より重要になります。
学習の位置づけ
初学者: まず「30秒で分かる結論」「直感で掴む」だけ読めば、 論文に出てきたときに「あ、 あれね」と分かります。
中級者: 数式と Python 実装をセットで覚え、 自分の手元データに適用できる状態を目指します。
上級者: 落とし穴と派生手法を理解し、 場面に応じた使い分け・改良ができることが目標です。
🔍 近接概念との比較
同じ データエンジニアリング カテゴリにある近接概念と、 SQL はどう違うのか? 混同しがちなポイントを整理します。
観点 SQL 近接概念
目的 主に SQL 固有の課題 (本文参照) 近接概念は関連はするが目的が異なる (本文の「関連手法・派生」参照)
前提条件 本文「前提・落とし穴」参照 手法ごとに前提が異なるため要確認
出力 数値 / 確率 / 集合など (上記公式参照) 同じ入力に異なる粒度の出力を返すことが多い
適用場面 本文「いつ使うか」参照 同じ問題でも視点が異なる手法を組み合わせるのが定石
計算コスト 用途範囲に応じて妥当な水準 精度と引き換えにコストが増える派生がある
📌 使い分けの原則: まずは本ページの定義を押さえ、 次に「🌐 関連手法・派生」「🔗 関連用語」のリンクから近接概念を確認し、 自分の問題に対してどれを使うか意識的に 選ぶことを習慣にしてください。
❓ よくある質問 (FAQ)
本サイトの教材を読み進めるなかで、 受講者からよく質問される項目をまとめました。
Q1. SQL を覚えるべき優先度は?
A. 論文を読んだり、 業務で類似の分析に出会うときに必ず登場します。 「30秒で分かる結論」までは押さえておけば、 都度本ページを参照しながら作業すれば十分です。 全暗記は不要、 引き出しに入れておく 感覚で OK。
Q2. 数式が苦手だが大丈夫?
A. 大丈夫。 まず「直感で掴む」「実値で計算してみる」を読み、 そのあと「定義・数式」に戻ると、 記号の意味が腑に落ちます。 数式は 後追い で構いません。 重要なのは、 結果の数字を見たときに、 何を意味するか言葉で説明できる ことです。
Q3. Python が動かないときは?
A. まず pandas や scikit-learn が pip install されているか確認。 SSDSE 系の CSV は encoding='utf-8' または 'cp932' で読めることが多く、 skiprows=1 でヘッダー行を飛ばすケースが大半。 列名が違うときは df.columns で確認して書き換えてください。
Q4. もっと深く学びたい場合は?
A. ページ末尾の「📚 関連グループ教材・さらに学ぶには」に紹介した書籍・オンライン教材へ。 加えて、 「🔗 関連用語」 から派生概念を順に学ぶと、 体系として理解が深まります。
Q5. 論文で SQL をどう報告すべき?
A. 「定義 → 使った理由 → 数値結果 → 解釈」の順で書くと読みやすくなります。 結果は 数値だけでなく不確実性 (CI・SE) も併記し、 限界 (適用範囲外の主張は避ける) も明示するのが現代的な書き方です。
✅ 実務チェックリスト
分析作業のなかで SQL を使うときは、 以下のチェックリストを上から順に確認してください。 抜けがあると後工程で痛い目に遭います。
① 分析設計フェーズ
□ 目的を 1 文で書ける か? (「何を、 どうしたいか」)
□ SQL がその目的に 本当に 合っているか?
□ 必要なデータの種類・量・期間を見積もったか?
□ 結果をどう報告・意思決定に使うか、 事前に決めたか?
② データ準備フェーズ
□ データの出典・取得日 を記録したか? (再現性)
□ 列の尺度 (名義 / 順序 / 間隔 / 比例) を確認したか?
□ 欠損 ・外れ値 の方針を決めたか?
□ サンプルサイズ は手法の最低要件を満たしているか?
③ 分析実行フェーズ
□ 前提条件 を満たしているか診断したか?
□ 結果は複数手法でクロスチェック したか?
□ コードは Git で管理 しているか?
□ 結果が 外れ値 1 件で激変 しないか確認したか?
④ 解釈・報告フェーズ
□ 数値 と不確実性 (CI / SE) を併記したか?
□ 「相関 ≠ 因果 」の境界を踏み越えていないか?
□ 適用範囲外 への拡張主張を避けたか?
□ 限界・前提 を明示したか?
📝 レポート・論文での書き方
論文・社内レポート・ステークホルダー報告書で SQL を扱うとき、 含めるべき項目とテンプレートをまとめました。
必須記載項目
項目 具体例
データ出典 独立行政法人統計センター SSDSE-B-2026 を加工
サンプルサイズ n=47 (47都道府県、 2023年データ)
使用変数 目的変数:医療費 / 説明変数:高齢化率、 人口密度
分析手法 SQLを適用 (scikit-learn 1.4 / Python 3.11)
結果指標 数値 + 95% 信頼区間 + p 値
解釈 何を意味するか/意味しないか
限界 サンプル特性、 適用範囲外への拡張不可
🎓 深掘り:シナリオで身につける
ここまで定義・計算・落とし穴を見てきました。 ここでは SQL をより深く理解するための思考フレーム と実務シナリオ を、 ストーリー形式で整理します。 用語そのものより、 「どんなときに思い出して、 どう使うか 」を体に染み込ませることが、 教材を読む真の目的です。
シナリオ A:研究室での卒論データ分析
「卒業研究で 47 都道府県のデータを分析したい」。 そんなとき SQL はどう登場するでしょうか。 担当の先生から「データを見たうえで、 関連する手法を 1 つ選んで適用してきて」と言われたとします。 まずデータの性質 (量・尺度・期間) を確認し、 「SQL がこの問題に合っているか」を本ページの 30 秒結論で照らし合わせます。 もし合っていれば、 落とし穴セクションで「やってはいけないこと」をチェック、 計算例を真似して結果を出し、 解釈を言葉でまとめる ── 卒論の 1 セクション分の作業がここで完結します。
シナリオ B:データサイエンスのインターン
企業のインターンで「過去 3 年の顧客データから来期の予測モデルを作って」と任された。 上司は SQL を当然知っている前提で話します。 言葉が通じないと議論についていけません。 そこで本ページの「定義・数式」「Python 実装」を 30 分で 押さえ、 上司の使う用語に追随する ── ジャストインタイム学習の典型シーンです。 後日、 自分でも実装した結果を上司に説明するとき、 「レポート・論文での書き方」テンプレートに沿って書けば、 過不足なく伝えられます。
シナリオ C:論文を読んでつまずいたとき
本サイトのトップから論文一覧をたどり、 ある論文を読んでいたら SQL が出てきた。 「これ、 なんだっけ?」と思った瞬間、 本ページに飛んでくる ── これが ジャストインタイム型教材 の使い方です。 30 秒結論を読み、 「あ、 そういう意味か」と納得したら、 元の論文に戻ります。 必要に応じて落とし穴セクションだけ読んで、 著者の解釈が妥当か批判的に確認することも可能です。
よくある誤解 3 連続
誤解 1:「SQL は常に最強の選択肢」
どんな手法にも適用範囲があります。 「SELECT * の濫用」のように、 前提を踏まえずに使うと結論を誤ります。 本ページの「落とし穴」「前提条件」を毎回必ず確認する習慣を。
誤解 2:「数式が分からないと使えない」
逆です。 まず Python 実装で結果を出してから、 数式に戻ると「なるほど、 ここが分子で、 ここが分母か」と腑に落ちます。 数式は 結果の意味を説明する補助 として使ってください。
誤解 3:「1 度読めば全部分かる」
分かりません (と断言します)。 概念は使ってこそ 身に付きます。 卒論や業務で実際にデータに当てはめ、 結果を解釈し、 説明する経験を 3 回くらい繰り返したら、 ようやく自分のものになります。 本ページは その傍らに置いておく辞書 として使ってください。
意思決定フレーム:使う?使わない?
状況 判断
前提条件が満たされている ✅ 適用 OK。 落とし穴に注意しつつ進める。
サンプル数が不足 ⚠️ 慎重に。 信頼区間が広くなり結論が出ない可能性。
前提が破れている (例:独立性なし) ❌ 別手法を検討。 関連手法・派生セクションを参照。
因果を主張したい ❌ SQL 単独では因果は言えない。 RCT/操作変数等を併用。
解釈が直感に反する 🔍 まず再現性確認 → 可視化 → 単純モデルとのクロスチェック。
🎯 このページのまとめ
📌 1 ページまとめ
SQL (データエンジニアリング) は、 リレーショナルDBの操作言語
要点: 表形式データの問い合わせ・更新 のための言語。
次のステップ: 本ページの「🔗 関連用語」から派生概念をたどるか、 「📚 さらに学ぶには」の書籍・教材で深く学んでください。 そして何より、 自分の手でデータに当てはめて結果を出す のが一番の理解の近道です。 ジャストインタイム型教材として、 必要なときに何度でも戻ってきてください。
🧭 サイト内ナビゲーション
本ページは、 統計・データ解析コンペティションの再現論文集に付随する用語解説の 1 ページです。 SQL 以外の用語も、 同じフォーマットで以下からたどれます。
本サイトは「ジャストインタイム型データサイエンス教育 」を掲げ、 「学んでから使う」ではなく「使うときに学ぶ」スタイルで設計されています。 ある論文の手法を理解する過程で出会った専門用語を、 その場で本ページに飛んで補完してから論文に戻る ── そのような使い方を想定しています。
🧭 用語固有の深堀り:SQL 4 要素ナラティブ
ここからは SQL という用語固有の 4 つの視点(定義/実行順序/コード解釈/落とし穴の理由)を、 ひとつのストーリーとして連結して読み解きます。 SQL は教科書で学ぶと「文法の暗記」になりがちですが、 実際の威力は 宣言的な思考 と 集合論的な操作 を頭の中で同居させたときに発揮されます。 本セクションでは、 SSDSE-B-2026 の都道府県データを SQLite に読み込んで操作する場面を軸に、 4 視点を行き来します。
要素 1 :定義のナラティブ ── なぜ SQL が 50 年間生き残っているか
SQL (Structured Query Language)は 1974 年に IBM の Chamberlin と Boyce が考案し、 1986 年に ANSI 標準化されました。 同時代の手続き型データ操作言語(IMS の DL/I など)は淘汰され、 SQL だけが残ったのは 「何が欲しいかだけ書く」宣言型 という設計哲学が圧倒的に強力だったからです。 「東京都の 2026 年の出生数を取りたい」と思ったとき、 SQL は SELECT 出生数 FROM 都道府県 WHERE 名前='東京' AND 年=2026 と書くだけ。 「どう取るか」(インデックス使用・JOIN 順序・並列化)は DB エンジンの最適化器が自動決定します。 47 都道府県の SSDSE-B-2026 をはじめ、 数兆行の Google スケールテーブルまで、 同じ構文で扱えるのが SQL の本質的価値です。 アナリストの仕事の 8 割は SQL で書ける ── これは誇張ではなく統計的事実で、 Stack Overflow Developer Survey でも SQL は毎年「最も使われる技術」上位に入ります。
要素 2 :実行順序のナラティブ ── 書き順と実行順は違う
SQL の初学者が必ず躓くのが「書き順と実行順序の乖離 」です。 書くときは SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY の順ですが、 論理的な実行順序は FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT です。 SSDSE-B-2026 で「人口 100 万人以上の県を地方別に集計し、 出生数の多い順に上位 5 件」を取りたいなら、 「FROM = SSDSE-B-2026 テーブル」「WHERE = A1101 >= 1000000」「GROUP BY = 地方」「HAVING = COUNT(*) >= 3」「SELECT = 地方, AVG(A4101) AS 平均出生数」「ORDER BY = 平均出生数 DESC」「LIMIT = 5」という思考で頭の中の表が段階的に絞り込まれていきます。 この実行順序を意識しないと、 WHERE で集約後の値を参照しようとして「カラムが見つからない」エラーに陥ります(その用途には HAVING を使う)。
要素 3 :コード解釈のナラティブ ── pandas との往復
本サイトでは SSDSE-B-2026 を pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1]) で DataFrame に読み込み、 df.to_sql('pref', sqlite3.connect(':memory:')) で in-memory DB に転送できます。 ここから SQL クエリ SELECT Prefecture, A4101 FROM pref WHERE A1101 > 1000000 ORDER BY A4101 DESC LIMIT 5 を pd.read_sql で発行すれば、 結果は DataFrame として返ってきます。 つまり「pandas → SQLite → SQL → pandas」のラウンドトリップを 5 行で書ける。 これが、 学生でも研究室でも企業でも SQL を学ぶ価値がある理由です。 pandas の df.query()・df.groupby()・df.merge() は SQL の WHERE・GROUP BY・JOIN に 1 対 1 で対応し、 どちらかを覚えればもう一方の理解が加速します。
要素 4 :落とし穴の理由ナラティブ ── N+1 問題と JOIN 爆発
SQL 経験者が必ず一度はハマるのが「N+1 問題 」と「JOIN の組み合わせ爆発」です。 N+1 問題はアプリケーション側で「1 回のクエリで親レコード取得 → 各親に対し子レコードを 1 回ずつクエリ」というパターンで発生し、 1 + N 回のクエリが走ります。 47 都道府県の親レコードに紐づく子(年齢別人口・出生数の月別集計など)を取るとき、 ナイーブに実装すると 1 + 47 = 48 回のクエリ発行。 正しくは JOIN で 1 回のクエリにまとめます。 JOIN の組み合わせ爆発は、 結合条件を忘れたり間違えると行数が「左の行数 × 右の行数」となり、 47 × 47 = 2209 行が返ってくる ── というケース。 「結果が想定より多い」ときは即座に EXPLAIN で実行計画を確認するのが習慣。
📖 数式を言葉で読み解く(拡張版・500 字+)
SQL の基本構文 $\text{SELECT cols FROM table WHERE cond GROUP BY g HAVING f ORDER BY o LIMIT n}$ を関係代数の言葉に翻訳すると次のようになります。 まず FROM table は「関係 $R$ を取り出す 」操作(基底テーブル選択)。 次に WHERE cond は関係代数の選択演算 $\sigma_{\text{cond}}(R)$ に対応し、 「条件 cond を満たす行だけを残す 」操作。 SSDSE-B-2026 で WHERE A1101 > 1000000 なら「人口 100 万人以上の県だけを残す」操作です。
続いて GROUP BY g は関係代数の標準演算には存在しない集約のための準備 で、 「同じ $g$ の値を持つ行をまとめてグループ化」する操作。 SSDSE-B-2026 で GROUP BY 地方 なら 47 都道府県を 8 地方(北海道・東北・関東・中部・近畿・中国・四国・九州)にまとめます。 HAVING f はグループに対する選択演算で、 「グループ単位の条件 $f$ を満たすグループだけ残す 」。 集約関数 SUM・AVG・COUNT・MIN・MAX の結果に対する絞り込みに使います。
そして SELECT cols は関係代数の射影演算 $\pi_{\text{cols}}$ に対応し、 「指定した列だけを残す 」。 集約関数(COUNT, SUM, AVG, MIN, MAX)もここで適用されます。 最後の ORDER BY o LIMIT n は関係代数の純粋演算ではなく、 「列 $o$ で並べ替えて上位 $n$ 件 」を返す表示制御。 関係代数では順序は本来定義されない(集合論的)ため、 SQL の ORDER BY は「結果の表示」の付加機能と理解するのが正確です。 SSDSE-B-2026 で人口降順上位 5 県を出すなら ORDER BY A1101 DESC LIMIT 5。 このように、 SQL の各キーワードは関係代数の演算に対応する と分かると、 単なる文法暗記から論理的な操作の連鎖として理解できるようになります。
📊 SSDSE-B-2026 実データで読み解く SQL
SSDSE-B-2026 を SQLite に読み込み、 段階的に SQL クエリを組み立てる実例を示します。 pandas との連携で「読み込み → クエリ → 結果取得」を 5 行で書けるのが、 現代データ分析の標準パターンです。
表:SQL の主要キーワード一覧(必修 12 種)
キーワード
用途
SSDSE-B-2026 での例
SELECT 返す列の指定 SELECT Prefecture, A4101
FROM テーブル指定 FROM pref
WHERE 行レベルの条件 WHERE A1101 > 1000000
GROUP BY 集約のキー GROUP BY 地方
HAVING グループ条件 HAVING COUNT(*) >= 3
ORDER BY 並べ替え ORDER BY A4101 DESC
LIMIT 上位 N 件 LIMIT 5
JOIN テーブル結合 p JOIN family ON p.Code=f.Code
DISTINCT 重複排除 SELECT DISTINCT 地方
UNION 結果の縦結合 2 県集合の合併
CASE 条件分岐 人口階級フラグ生成
WITH (CTE) サブクエリ命名 中間結果の整理
Python 実装:SQLite で SSDSE-B-2026 を集計
📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年)
年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) …
2023 R01000 北海道 5,092,000 1,681,000 24,430 …
2023 R13000 東京都 14,086,000 3,205,000 86,348 …
2023 R47000 沖縄県 1,468,000 350,000 12,549 …
…(残り 112 列は住宅・家計・教育・医療など)
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import pandas as pd
import sqlite3
df = pd . read_csv ( 'data/raw/SSDSE-B-2026.csv' , header = 0 , encoding = 'cp932' )
# 1 行目を見出しにしたので、2 行目の日本語名の行を落として数値に直す
df = df [ df [ df . columns [ 1 ]] . astype ( str ) . str . match ( r '^R\d {5} $' , na = False )] . copy ()
for _c in df . columns [ 3 :]:
df [ _c ] = pd . to_numeric ( df [ _c ], errors = 'coerce' )
conn = sqlite3 . connect ( ':memory:' )
df . to_sql ( 'pref' , conn , index = False )
query = """
SELECT Prefecture, A1101 AS 人口, A4101 AS 出生数
FROM pref
WHERE A1101 > 1000000
ORDER BY A4101 DESC
LIMIT 5
"""
print ( pd . read_sql ( query , conn ))
📤 実行例(実測)
Prefecture 人口 出生数
0 東京都 13515271 113194
1 東京都 13646000 111964
2 東京都 13399000 110629
3 東京都 13307000 109986
4 東京都 13768000 108990
Python 実装:地方別集計+HAVING
📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年)
年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) …
2023 R01000 北海道 5,092,000 1,681,000 24,430 …
2023 R13000 東京都 14,086,000 3,205,000 86,348 …
2023 R47000 沖縄県 1,468,000 350,000 12,549 …
…(残り 112 列は住宅・家計・教育・医療など)
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import pandas as pd
import sqlite3
df = pd . read_csv ( 'data/raw/SSDSE-B-2026.csv' , header = 0 , encoding = 'cp932' )
# 1 行目を見出しにしたので、2 行目の日本語名の行を落として数値に直す
df = df [ df [ df . columns [ 1 ]] . astype ( str ) . str . match ( r '^R\d {5} $' , na = False )] . copy ()
for _c in df . columns [ 3 :]:
df [ _c ] = pd . to_numeric ( df [ _c ], errors = 'coerce' )
conn = sqlite3 . connect ( ':memory:' )
df . to_sql ( 'pref' , conn , index = False )
q = """
SELECT
CASE WHEN A1101 >= 5000000 THEN '大規模'
WHEN A1101 >= 1000000 THEN '中規模'
ELSE '小規模' END AS 規模,
COUNT(*) AS 県数,
AVG(A4101) AS 平均出生数,
SUM(A1101) AS 総人口
FROM pref
GROUP BY 規模
HAVING 県数 >= 3
ORDER BY 総人口 DESC
"""
print ( pd . read_sql ( q , conn ))
📤 実行例(実測)
規模 県数 平均出生数 総人口
0 大規模 108 55938.314815 824352477
1 中規模 341 12511.501466 602278993
2 小規模 115 5440.547826 90916374
Python 実装:Window 関数で都道府県の人口順位を取得
📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年)
年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) …
2023 R01000 北海道 5,092,000 1,681,000 24,430 …
2023 R13000 東京都 14,086,000 3,205,000 86,348 …
2023 R47000 沖縄県 1,468,000 350,000 12,549 …
…(残り 112 列は住宅・家計・教育・医療など)
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import pandas as pd
import sqlite3
df = pd . read_csv ( 'data/raw/SSDSE-B-2026.csv' , header = 0 , encoding = 'cp932' )
# 1 行目を見出しにしたので、2 行目の日本語名の行を落として数値に直す
df = df [ df [ df . columns [ 1 ]] . astype ( str ) . str . match ( r '^R\d {5} $' , na = False )] . copy ()
for _c in df . columns [ 3 :]:
df [ _c ] = pd . to_numeric ( df [ _c ], errors = 'coerce' )
conn = sqlite3 . connect ( ':memory:' )
df . to_sql ( 'pref' , conn , index = False )
q = """
SELECT Prefecture, A1101,
RANK() OVER (ORDER BY A1101 DESC) AS 人口順位,
PERCENT_RANK() OVER (ORDER BY A1101) AS 百分位
FROM pref
ORDER BY 人口順位
LIMIT 10
"""
print ( pd . read_sql ( q , conn ))
📤 実行例(実測)
Prefecture A1101 人口順位 百分位
0 東京都 14086000 1 1.000000
1 東京都 14047594 2 0.998224
2 東京都 14038000 3 0.996448
3 東京都 14010000 4 0.994671
4 東京都 14007000 5 0.992895
5 東京都 13887000 6 0.991119
6 東京都 13768000 7 0.989343
7 東京都 13646000 8 0.987567
8 東京都 13515271 9 0.985790
9 東京都 13399000 10 0.984014
📚 事例集
事例 1:日別売上集計
EC サイトのトランザクションテーブルから「日別・商品別売上トップ 10」を Window 関数で取得。 RANK() OVER (PARTITION BY date ORDER BY sales DESC)。
事例 2:コホート分析
ユーザの初回購入月と購入回数を join し、 月別リテンション率を可視化。 SQL 1 クエリで完結可能。
事例 3:A/B テスト結果集計
グループ A/B のコンバージョン率を CASE 文で生成、 t 検定の入力を SQL で準備。 統計検定は別ツールで実施。
事例 4:データ品質チェック
「欠損行数 / 重複行数 / 外れ値件数」を 1 SQL で集計し、 ETL パイプラインで定期実行 → Slack 通知。
事例 5:BigQuery で TB 級分析
Google Analytics の生データ(1 日 1 億行)を BigQuery で集計。 同じ SQL で SQLite と互換性があるので、 ローカル開発 → クラウド本番の移行が容易。
🪟 Window 関数完全ガイド
Window 関数(OVER 句)は 2003 年に標準入りした強力機能で、 集約しつつ全行を保持できます。 SSDSE-B-2026 の都道府県データで、 ランキング・累積・移動平均などを 1 クエリで計算できます。
主要 Window 関数一覧
関数
用途
SSDSE 適用例
ROW_NUMBER() 行番号(同順位なし) 人口順の通し番号
RANK() 順位(同順位は同じ、 飛び番) 人口ランキング
DENSE_RANK() 順位(飛び番なし) 高齢化率の順位(同値考慮)
NTILE(N) N 分位グループ 県を 4 分位に分ける
LAG(col) 前行の値 時系列の前年差
LEAD(col) 次行の値 時系列の翌年予測比較
FIRST_VALUE / LAST_VALUE 窓の最初/最後 最上位県との比較
SUM() OVER 累計 人口の累計
AVG() OVER 移動平均 3 県移動平均
PERCENT_RANK() 百分位 県別人口の百分位
Window 関数の構文
function() OVER (PARTITION BY col1 ORDER BY col2 ROWS BETWEEN ...)
PARTITION BY :グループ分け(地方別、 性別別など)
ORDER BY :窓内の順序
ROWS / RANGE :窓の範囲(移動平均の幅など)
🔁 CTE と再帰クエリ
WITH 句(Common Table Expression)は SQL の可読性を劇的に高める機能です。 さらに RECURSIVE を付けると階層データを扱えます。
基本 CTE
📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年)
年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) …
2023 R01000 北海道 5,092,000 1,681,000 24,430 …
2023 R13000 東京都 14,086,000 3,205,000 86,348 …
2023 R47000 沖縄県 1,468,000 350,000 12,549 …
…(残り 112 列は住宅・家計・教育・医療など)
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import pandas as pd
import sqlite3
df = pd . read_csv ( 'data/raw/SSDSE-B-2026.csv' , header = 0 , encoding = 'cp932' )
# 1 行目を見出しにしたので、2 行目の日本語名の行を落として数値に直す
df = df [ df [ df . columns [ 1 ]] . astype ( str ) . str . match ( r '^R\d {5} $' , na = False )] . copy ()
for _c in df . columns [ 3 :]:
df [ _c ] = pd . to_numeric ( df [ _c ], errors = 'coerce' )
conn = sqlite3 . connect ( ':memory:' )
df . to_sql ( 'pref' , conn , index = False )
q = """
WITH ranked AS (
SELECT Prefecture, A1101,
RANK() OVER (ORDER BY A1101 DESC) AS rk
FROM pref
),
top5 AS (
SELECT * FROM ranked WHERE rk <= 5
)
SELECT * FROM top5
"""
print ( pd . read_sql ( q , conn ))
📤 実行例(実測)
Prefecture A1101 rk
0 東京都 14086000 1
1 東京都 14047594 2
2 東京都 14038000 3
3 東京都 14010000 4
4 東京都 14007000 5
再帰 CTE の例(フィボナッチ数列)
📋 コピー 1
2
3
4
5
6
7
8
9
10
11
12 import pandas as pd
import sqlite3
conn = sqlite3 . connect ( ':memory:' )
q = """
WITH RECURSIVE fib(n, a, b) AS (
VALUES (1, 0, 1)
UNION ALL
SELECT n+1, b, a+b FROM fib WHERE n < 10
)
SELECT n, a FROM fib
"""
print ( pd . read_sql ( q , conn ))
📤 実行例(実測)
n a
0 1 0
1 2 1
2 3 1
3 4 2
4 5 3
5 6 5
6 7 8
7 8 13
8 9 21
9 10 34
🚨 SQL アンチパターン 10 連発
AP-1 :SELECT *
本番では必要列だけ。 列追加でアプリが壊れる原因にも。
AP-2 :暗黙の CROSS JOIN
JOIN ON 忘れで行数爆発。 必ず明示的に書く。
AP-3 :N+1 クエリ
ループ内でクエリ発行。 JOIN または IN 句で 1 クエリにまとめる。
AP-4 :DISTINCT で重複「ごまかし」
DISTINCT で隠すより、 そもそも JOIN を見直す。
AP-5 :LIKE '%xxx%' でフルスキャン
前方一致のみインデックス活用可。 全文検索が必要なら FTS 専用機能。
AP-6 :WHERE 句で関数適用
WHERE LOWER(col) = 'x' はインデックス無効化。 関数インデックスを使うか保存時に正規化。
AP-7 :NULL 比較に =
= NULL は常に偽。 IS NULL を使う。
AP-8 :トランザクション欠落
更新を BEGIN/COMMIT で囲まないと中途半端な状態に。
AP-9 :可変文字列連結
SQL インジェクションの温床。 プレースホルダ必須。
AP-10 :本番でテーブル DROP
取り返しがつかない。 必ず BEGIN; 〜 ROLLBACK でテスト。
❓ 追加 FAQ
Q6. SQL と NoSQL の使い分けは?
A. 構造化データ・ACID 重視なら SQL(RDBMS)。 スキーマレス・水平分散重視なら NoSQL(MongoDB・Cassandra)。 詳細は
NoSQL ページ参照。
Q7. ORM は SQL の代わりになる?
A. 単純な CRUD なら ORM で十分。 ただし複雑な分析クエリは ORM のオーバーヘッドが大きく、 結局 raw SQL を書くことに。 両方使えるのがプロ。
Q8. pandas vs SQL どちらが速い?
A. データサイズ次第。 メモリに乗る規模なら pandas、 大規模なら SQL(特に列指向 DB)。 DuckDB は両方の長所を持つ。
Q9. SQL の学習教材は?
A. PostgreSQL 公式ドキュメント、 SQLBolt(インタラクティブ)、 LeetCode の SQL 問題集が定番。
Q10. SQL は将来 AI に置き換わる?
A. 自然言語からの SQL 自動生成(Text-to-SQL)は実用化しつつある。 しかし「何を聞きたいか」を言葉で表現するスキル自体は SQL の論理と等価。 SQL 思考は残ると予想。
📕 推奨文献
Codd, E. F. (1970) "A Relational Model of Data for Large Shared Data Banks" — 関係モデルの原典
『達人に学ぶ SQL 徹底指南書』(ミック)— 日本語の名著
『SQL アンチパターン』(Bill Karwin)— アンチパターンの教科書
『SQL クックブック』(Anthony Molinaro)— 実用クエリ集
『SQL パフォーマンスチューニング』(Markus Winand)— インデックス設計の決定版
PostgreSQL 公式ドキュメント — 機能網羅・無料
SQLBolt(sqlbolt.com)— インタラクティブ学習
LeetCode SQL 問題集 — 面接対策・実力試し
🏆 SQL クエリ品質評価 15 項目
#
評価項目
配点
1 SELECT * を使っていない 1
2 JOIN ON が明示 1
3 NULL 処理が正しい 1
4 プレースホルダ使用(SQLi 対策) 1
5 EXPLAIN で実行計画を確認 1
6 適切なインデックス活用 1
7 CTE で複雑性を分解 1
8 列名にエイリアス明示 1
9 コメントで意図を記述 1
10 予約語は大文字に統一 1
11 トランザクション制御適切 1
12 サブクエリ過剰でない 1
13 結果件数を確認 1
14 期待スキーマと照合 1
15 Git でクエリを版管理 1
🎯 SQL を「使える」5 段階ロードマップ
レベル 1(認識):基本構文を知る
SELECT・FROM・WHERE・GROUP BY・JOIN の意味を口頭で説明できる。
レベル 2(読解):他人の SQL を理解できる
他人のクエリを見て、 「何を取ろうとしているか」「どこで絞り込んでいるか」「集約のキーは何か」を判定できる。 実行順序の論理を頭の中で再生できる。
レベル 3(記述):単純なクエリを書ける
SSDSE-B-2026 で「上位 N 件」「条件付き集計」「JOIN」を自力で書ける。 pandas との対応も理解。
レベル 4(応用):Window 関数・CTE を駆使
ランキング・累積・移動平均を 1 クエリで書ける。 複雑な集計を CTE で分解して可読性を保てる。 N+1 問題を回避し、 EXPLAIN で実行計画を確認する習慣がある。
レベル 5(設計):パフォーマンスを最適化
インデックス設計、 パーティショニング、 マテリアライズドビューを判断できる。 ORM の生成 SQL を読みつつ、 必要なら raw SQL に降りる。 ETL パイプライン全体を SQL ベースで設計できる。 ここまで来れば、 データエンジニアとして名乗れます。
📋 SSDSE-B-2026 でよく使うクエリ 12 選
SSDSE-B-2026 を SQLite に読み込んだ後、 すぐ使える定番クエリを 12 個まとめました。 すべて pandas + sqlite3 で動作確認済み。
クエリ 1:基本確認
SELECT COUNT(*) AS n_pref, MIN(A1101) AS min_pop, MAX(A1101) AS max_pop FROM pref;
クエリ 2:人口降順上位 10
SELECT Prefecture, A1101 AS pop FROM pref ORDER BY A1101 DESC LIMIT 10;
クエリ 3:高齢化率上位 10
SELECT Prefecture, (A1303 * 1.0 / A1101) AS aging FROM pref ORDER BY aging DESC LIMIT 10;
クエリ 4:出生数 / 死亡数の比較
SELECT Prefecture, A4101 AS births, A4200 AS deaths, (A4101 - A4200) AS natural_change FROM pref ORDER BY natural_change DESC LIMIT 10;
クエリ 5:人口階級ごとの件数
SELECT CASE WHEN A1101 >= 5000000 THEN '大' WHEN A1101 >= 1000000 THEN '中' ELSE '小' END AS scale, COUNT(*) FROM pref GROUP BY scale;
クエリ 6:上位 5 県の人口合計
SELECT SUM(A1101) FROM (SELECT A1101 FROM pref ORDER BY A1101 DESC LIMIT 5);
クエリ 7:Window で順位付与
SELECT Prefecture, A1101, RANK() OVER (ORDER BY A1101 DESC) AS rk FROM pref ORDER BY rk LIMIT 10;
クエリ 8:CTE で複数段階の絞り込み
WITH a AS (SELECT *, (A1303 * 1.0 / A1101) AS aging FROM pref) SELECT Prefecture, aging FROM a WHERE aging > 0.3 ORDER BY aging DESC;
クエリ 9:累積
SELECT Prefecture, A1101, SUM(A1101) OVER (ORDER BY A1101 DESC) AS cum FROM pref ORDER BY A1101 DESC LIMIT 10;
クエリ 10:平均との差
SELECT Prefecture, A1101 - AVG(A1101) OVER () AS diff_from_mean FROM pref ORDER BY diff_from_mean DESC LIMIT 10;
クエリ 11:4 分位グループ
SELECT Prefecture, NTILE(4) OVER (ORDER BY A1101) AS quartile FROM pref;
クエリ 12:JSON 抽出(PostgreSQL 例)
SELECT data->>'prefecture', data->>'pop' FROM logs WHERE (data->>'pop')::int > 1000000;
🔍 エッジケース集
ケース 1:日本語列名のエスケープ
SSDSE は SHIFT-JIS 行に日本語列名がある。 SQLite では英字 ASCII 列名で扱うのが安全(A1101 など)。 PostgreSQL ならダブルクォートでエスケープ:"都道府県"。
ケース 2:整数除算による精度損失
A1303 / A1101 は整数除算で 0 になる。 A1303 * 1.0 / A1101 で実数化。
ケース 3:日付の境界条件
WHERE date <= '2026-05-23' で「23 日 23:59:59 を含むか」が DB によって違う。 date < '2026-05-24' と書くのが安全。
ケース 4:エンコーディング
UTF-8 と Shift-JIS の混在は文字化けの温床。 接続時に SET NAMES utf8mb4 を発行。
ケース 5:LIMIT が遅い
大きな OFFSET(LIMIT 100 OFFSET 1000000)は遅い。 カーソルベースのページネーション(WHERE id > last_id)に置き換える。
ケース 6:COUNT(*) の遅さ
PostgreSQL では COUNT(*) が遅い。 概算でよいなら pg_class.reltuples を見る。
ケース 7:ロック
大きな UPDATE 中にテーブルがロックされ、 他のクエリが待たされる。 バッチで小分けに更新する。
🧩 SQL と他手法の統合
統合 1:SQL → pandas → 機械学習
SQL で前処理(JOIN・集約) → pandas にロード → scikit-learn でモデリング。 役割分担の鉄則は「集合的操作は SQL、 行単位の特徴量計算は pandas」。
統合 2:SQL → BI ツール
Metabase・Tableau・Looker はすべて SQL を背後で発行。 ダッシュボードの裏側を理解するには SQL リテラシーが不可欠。
統合 3:SQL → ETL → DWH
dbt(data build tool)で SQL ベースのデータ変換パイプライン構築。 「SQL を Git で管理する」モダンデータスタックの中核。
統合 4:SQL + Python + Jupyter
Jupyter のセル内で %%sql マジックコマンドを使い、 SQL とノートブック分析を統合。 探索的データ分析(EDA)の定番。
🎮 触って理解する — SQL クエリビルダー
下のパネルで SELECT / WHERE / GROUP BY / HAVING / ORDER BY / LIMIT の各句を UI で組み立てると、 対応する SQL 文がリアルタイムに生成され、 結果テーブルも即座に更新されます。 データは SSDSE-B-2026 の 2023 年・47 都道府県の実測値 (人口 = A1101、 出生数 = A4101)。 「地方」列は学習用に付加した一般的な 8 区分です。 スライダーやチェックボックスを動かして、 「句を 1 つ変えると結果集合がどう変わるか」を体感してください。
生成された SQL(リアルタイム更新)
実行順序パイプライン(書く順 ≠ 評価順)
SQL は SELECT から書き始めますが、 評価は FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT の順。 各段の箱をタップ / クリックすると説明が出ます。 灰色の箱は「このクエリでは働いていない句」です。
FROM 47行
WHERE 47行
GROUP BY —
HAVING —
SELECT 4列
ORDER BY —
LIMIT 5行
← 論理的な評価順(左から右)。 書く順とは異なる点に注意 →
数字は各段を通過した後の行数 / グループ数 / 列数
👆 パイプラインの箱をタップすると、 その句の役割がここに表示されます。
結果テーブル(5 行)
出典: 独立行政法人統計センター SSDSE-B-2026(2023 年、 47 都道府県)。 人口は総人口 A1101、 出生数は A4101 の実測値。 集計 (SUM / AVG / COUNT) はブラウザ内で正確に計算しています(AVG は小数第 1 位まで表示)。
💡 直感 — SQL は「集合への宣言的な問いかけ」
上のクエリビルダーで気づいてほしいのは、 あなたが一度も「ループ」や「変数」を書いていないことです。 チェックボックスとスライダーで「どんな結果集合が欲しいか」を宣言しただけ で、 計算の手順(どの行から見るか、 どう数えるか)は実行系が引き受けています。 これが SQL の宣言型パラダイムそのもの。 WHERE のスライダーを動かすと「47 都道府県の集合」から「条件を満たす部分集合」が瞬時に切り出され、 GROUP BY を「地方」にすると 47 行の集合が 8 グループの集合へと粒度ごと変換 されます。 「行の集合 → 部分集合 → グループの集合」という集合演算の連鎖として SQL を読めるようになれば、 どんなに長いクエリも怖くありません。
⚠️ よくある落とし穴 — このビルダーで体感できる 2 大混乱
① 実行順序と記述順序の違い
SQL 文は SELECT から書き始めますが、 上のパイプラインが示すとおり評価は FROM から。 SELECT は 5 番目です。 だから SELECT SUM(人口) AS 人口合計 と付けた別名を WHERE で使うとエラーになる(WHERE の時点で別名はまだ存在しない)のに、 ORDER BY 人口合計 は動く(ORDER BY は SELECT より後)。 ビルダーで GROUP BY を有効にして ORDER BY を「集計値」にすると、 まさにこの「後から使える別名」の挙動を再現しています。
② WHERE と HAVING の混同
WHERE は行 への条件(集約前)、 HAVING はグループ への条件(集約後)。 ビルダーで GROUP BY を「地方」にし、 WHERE スライダー(人口 ≥ X 万人)と HAVING スライダー(県数 ≥ N)を別々に動かしてみてください。 WHERE を上げると「集計に参加する県」が減って各地方の合計値が変わり、 HAVING を上げると「表示される地方」が丸ごと消えます。 集約値(COUNT や SUM)に対する条件を WHERE に書けないのは、 WHERE の評価時点でまだ集約が行われていないから ── パイプラインの並び順がその理由を説明しています。
🚀 発展 — JOIN・サブクエリ・ウィンドウ関数へ
JOIN(結合) : このビルダーは 1 テーブルですが、 実務では複数テーブルの結合が主戦場。 テーブル結合 ・外部結合 ・データ結合 の各ページで、 INNER / LEFT / FULL OUTER の違いを確認できます。 JOIN は実行順序の最初(FROM 段階)で行われるため、 結合直後の行数を COUNT(*) で検算する習慣が事故を防ぎます。
サブクエリ : 「平均人口を超える県」のように、 クエリの結果を条件に使う 入れ子構造。 WHERE 人口 > (SELECT AVG(人口) FROM pref) のように書きます。 深い入れ子は CTE(WITH 句)でフラット化するのが現代の作法です。
ウィンドウ関数 : GROUP BY が「行を潰して」集計するのに対し、 RANK() OVER (ORDER BY 人口 DESC) は行を潰さずに 順位や累積を併記できる上位互換的な機能。 本ページ「ウィンドウ関数完全ガイド」の節で SSDSE-B の実例を掲載しています。
pandas との対応 : このビルダーの各句は pandas の df[...](WHERE)・groupby (GROUP BY)・sort_values(ORDER BY)・head(LIMIT)に 1 対 1 で対応します。 片方を覚えればもう片方の理解が加速します。
🗺 概念マップ
SQL を中心に、 主要 RDBMS / DWH / OLAP エンジンと、 周辺ツール (BI / クライアント) との関係を示す。 中心ノードから 6 句構造 (SELECT/FROM/WHERE/GROUP BY/HAVING/ORDER BY) が放射状に伸びるイメージで、 SSDSE-B-2026 の都道府県データに対して同じ SQL がどのエンジンでも動く点が要諦。
SQL
PostgreSQL/MySQL
DuckDB
ClickHouse
BigQuery/Snowflake
Spark SQL
DBeaver/Grip
SQL の概念マップは「SELECT/FROM/WHERE/GROUP BY/HAVING/ORDER BY」の 6 句構造を中心に、 SSDSE-B-2026 を BigQuery / DuckDB / PostgreSQL のテーブルにロードして都道府県横断クエリを書くワークフローを表す。 結合 (JOIN) で SSDSE-A (家計調査) と SSDSE-B (基本指標) を地域コードで結びつけることが現実の分析現場で最も使う操作になる。
🔗 隣接手法への橋渡し
SQL は単体で完結するのではなく、 ETL ツール (Airflow / dbt) で SSDSE-B-2026 を取り込み、 BI ツール (Tableau / Looker) や Python (pandas.read_sql) と組み合わせて初めて分析パイプラインを構成する。 集計結果は JSON で API 公開され、 可視化ダッシュボードに渡るのが典型構成。
上流 : CSV / Parquet → DuckDB / BigQuery への取り込み (CSV / データクレンジング )
並列 : pandas の groupby().agg() や PySpark DataFrame API (pandas ) と相互変換
下流 : SELECT 結果を 可視化 / BI ツール に渡して解釈、 レポート化 へ展開
SQL を上流 (取り込み)・並列 (DataFrame API)・下流 (BI / API) と接続することで、 SSDSE-B-2026 の都道府県横断クエリから可視化までの分析パイプラインを再現可能に組める。
🌳 SQL vs pandas vs Spark 選択基準
条件
推奨ツール
理由
データ < 1GB pandas / SQLite メモリで処理可能
データ 1〜100GB PostgreSQL / DuckDB 単機 RDBMS で十分
データ > 100GB BigQuery / Snowflake クラウド DWH の出番
行ベース処理多用 pandas DataFrame の柔軟性
集合操作中心 SQL 関係代数の威力
分散処理必須 Spark SQL クラスタ実行
機械学習統合 pandas + sklearn Python エコシステム
📑 本サイト論文での SQL 登場場面
統計データ解析コンペ過去入賞論文では、 SQL は表立って出ることは少ないですが、 「前処理」「集計」「JOIN」のフェーズで陰の主役として活躍します。 公開コード(ipynb / py)を読むと、 多くは pandas で書かれていますが、 大規模な分析では BigQuery / SQLite に置き換えると速度が劇的に改善します。 本サイトの論文一覧 の「データ取得・前処理」セクションで SQL ベースの実装例が参照できます。
論文で書くべき SQL 記述項目
使用 RDBMS(SQLite 3.x / PostgreSQL 16 など)のバージョン明記
テーブル定義(CREATE TABLE)を付録に掲載
使ったクエリを完全な形で記載(部分抜粋は再現性なし)
処理時間を実測値で記録(n=47 は瞬時、 n=1 億は分単位)
結果件数を明示(期待件数と実件数の検証)
🌳 手法選択フロー
SSDSE-B-2026 を分析する際の SQL 利用判断は、 以下の Step で決まる。
Step 1: クエリは単発か繰り返しか?
単発の探索 (例: 47 都道府県の人口上位 10 件を見たい) → DuckDB + SELECT + ORDER BY で一発実行
毎日同じ集計を回す (例: 月次 KPI ダッシュボード) → BigQuery のスケジュールクエリや dbt model で固定化
Step 2: 結合 (JOIN) が必要か?
SSDSE-B (基本指標) のみで完結 → 単テーブルの WHERE + GROUP BY で十分
SSDSE-A (家計調査) と地域コードで紐付け → INNER JOIN / LEFT JOIN を地域コードで実行、 テーブル結合 参照
時系列結合 (年度ずらし) → LAG() / LEAD() ウインドウ関数を活用
Step 3: 出力は人間向けか機械向けか?
このフローで SQL を選ぶことで、 SSDSE-B-2026 の都道府県横断分析を再現性高く扱える。
🧭 深掘り解説 — SQL を「集合への宣言」として掴む
ここまでで構文と実例は一通り触れた。 この節では「直感 」「落とし穴 」「発展 」の 3 層に整理して、 SQL の考え方そのものを固める。 既出の各節と重複する話題もあるが、 ひとつの視点(集合への宣言的な問いかけ) で串刺しにするのが狙い。 数値例のうち実データは SSDSE-B-2026(総人口 A1101、 出生数 A4101 ほか 112 列)に基づき、 挙動説明のために作った小さな表は「架空 」と明記する。
🎨 直感 — 「どう取るか」ではなく「何が欲しいか」を書く
SQL は宣言型 言語である。 for ループも一時変数も書かず、 「欲しい結果集合の条件」だけを記述すると、 DB エンジンのクエリオプティマイザ が「どのインデックスを使い、 どの順で結合し、 どこで絞るか」という実行手順(実行計画)を自動で組み立てる。 同じ 1 本の SELECT でも、 データ量や統計情報が変われば内部の取り方は変わる ── これが「手続きを書かない」ことの旨みだ。
中核の 4 動作は集合演算 として読める。 SELECT は列の射影 (欲しい列だけ残す)、 WHERE は行の選択 (部分集合を切り出す)、 JOIN は 2 集合の直積 + 条件 (対応づけ)、 GROUP BY は行集合を同値類(グループ) へ畳み込む操作。 SSDSE-B なら「47 都道府県の集合 →(WHERE で)部分集合 →(GROUP BY 地方で)8 グループの集合」と、 粒度を変えながら集合を渡り歩くイメージで長いクエリも読み解ける。 前提となる表形式データの土台は リレーショナルDB / データベース の節を参照。
⚠️ 落とし穴 — 「動くが間違っている」を生む 8 つの罠
SQL のバグはエラーで止まらず、 静かに間違った件数・合計を返す のが厄介。 特に集計・結合・NULL が絡むと、 見た目は正しい結果に見えてしまう。
① NULL の三値論理 ── = NULL は永遠に真にならない
SQL の真偽は
TRUE / FALSE / UNKNOWN の 3 値。 NULL は「不明」を表し、
NULL = NULL も
NULL <> NULL も結果は UNKNOWN で、
WHERE は UNKNOWN の行を通さない。 だから欠損を拾うには
IS NULL /
IS NOT NULL を使う。 下は
架空 の真理値表(
x が NULL のとき):
式 評価 WHERE 通過?
x = NULLUNKNOWN 通さない
x <> 100UNKNOWN 通さない
x IS NULLTRUE 通す
NOT (x = 100)UNKNOWN 通さない
さらに
COUNT(*) は NULL 行も数えるが
COUNT(col) は col が NULL の行を除外し、
AVG(col) も NULL を分母から外す。 「件数が合わない」の典型原因。
NOT IN (サブクエリ) の中に NULL が 1 つでも混じると全体が UNKNOWN 化して 0 件になる事故も有名。
② JOIN の膨張 ── 多対多で行数が掛け算になる
結合キーが片側で重複していると、 出力行数は「左の一致数 × 右の一致数」に膨らむ。 SSDSE-B のように
(年度, 地域コード) が一意(
主キー )なら 1 対 1 で安全だが、 キーの一部を書き忘れる(例:
ON a.pref_code = b.pref_code だけで年度条件を落とす)と、 同一県 × 全 12 年が総当たりになり 12 倍に膨張する。
架空 の最小例:
左表 key 右表 key 結合後の行数
A(1 行) A(3 行) 3 行
A(2 行) A(3 行) 6 行
対策は 3 つ: (1) 結合直後に
COUNT(*) で行数を検算する、 (2) 結合キーが一意かを事前に
GROUP BY key HAVING COUNT(*)>1 で確認する、 (3) 集約してから結合する。 詳しくは
内部結合 /
外部結合 /
テーブル結合 /
データ結合 と
外部キー を参照。
③ GROUP BY と集約列の対応 ── 「集約されていない列」問題
GROUP BY を使うと、
SELECT に書ける列は
グループキーそのもの か
集約関数の中 に限られる。
SELECT 地方, pref_name, SUM(population) ... GROUP BY 地方 は、 各地方に県が複数あるため
pref_name を一意に決められず、 PostgreSQL では明確にエラーになる。 一方 MySQL(
ONLY_FULL_GROUP_BY 無効時)や SQLite は
黙って任意の 1 行 を返すため、 気づかないまま誤集計になりやすい。 「各グループの代表県」が欲しいなら、 ウィンドウ関数か相関サブクエリで明示的に選ぶ。 集約の基礎は
グループ集計 (groupby) を参照。
④ WHERE と HAVING の混同 ── 集約の「前」か「後」か
WHERE は集約前 の行フィルタ、 HAVING は集約後 のグループフィルタ。 「出生数合計が 10 万人を超える地方」を WHERE SUM(births) > 100000 と書くとエラー ── WHERE の評価時点でまだ SUM は計算されていない(本ページ「実行順序」節および 🎮 ウィジェットのパイプライン図を参照)。 正しくは GROUP BY 地方 HAVING SUM(births) > 100000。 逆に、 集約前に個々の県を除きたい(例: 特定年を外す)なら WHERE 側に書く。 両方を併用するのが実務では普通。
⑤ 暗黙の型変換 ── 文字列の "09" と数値の 9
SSDSE-B の地域コード Code は文字列(例 R01000)、 年度は数値化して使う。 CSV をそのまま取り込むと全列が TEXT になりやすく、 WHERE year = 2020 のつもりが文字列比較になって並び順・比較が狂う。 CAST(... AS INTEGER) で型を明示し、 数値列は数値、 キーは文字列、 と意図した型を宣言するのが安全。 RDBMS ごとに暗黙変換の規則が違う(比較不能時にエラーにする DB/黙って変換する DB)ため、 移植性の観点でも明示 CAST が推奨。
⑥ パフォーマンス ── フルスキャンとインデックス
47 行の SSDSE-B なら何をしても一瞬だが、 数億行では「WHERE の列にインデックスがあるか」で数千倍の差が出る。 インデックスがなければ全行を舐めるフルスキャン 、 あれば B-tree で目的行へ一足飛び。 ただし WHERE YEAR(date) = 2020 のように列を関数で包むとインデックスが効かなくなる(date >= '2020-01-01' AND date < '2021-01-01' と範囲に書き換える)。 実際にどう実行されるかは EXPLAIN / EXPLAIN QUERY PLAN で必ず確認する。
⑦ SQL インジェクション ── 文字列連結は禁忌
ユーザ入力を "... WHERE name='" + input + "'" のように文字列連結で組み立てると、 ' OR '1'='1 のような入力でクエリを乗っ取られる。 対策はプレースホルダ(パラメータ化クエリ) 一択: Python なら cur.execute("SELECT * FROM t WHERE code=?", (code,))。 値は SQL 文と分離して DB に渡され、 構文として解釈されない。 分析用途でも、 外部から受け取った県コードを埋め込むときは癖として必ずプレースホルダを使う。
⑧ 重複 ── DISTINCT と UNION ALL の取り違え
JOIN の膨張(罠②)で増えた行を SELECT DISTINCT で握りつぶすと、 本当は集計が二重になっているのに件数だけ辻褄が合って合計が過大 になる ── DISTINCT は原因療法ではない。 また UNION は重複を除去する(内部でソート/ハッシュが走り重い)のに対し、 UNION ALL は重複を残して単純連結 で速い。 「重複しないと分かっている」なら UNION ALL を選ぶ。 重複行そのものの検出は GROUP BY キー HAVING COUNT(*) > 1 が定石。
🚀 発展 — ここから先に効く道具立て
JOIN 種別(INNER / OUTER)
INNER JOIN は両側に一致がある行だけ、 LEFT OUTER JOIN は左を全部残し右が無ければ NULL、 FULL OUTER JOIN は両側の和集合。 「一致しなかった側」を分析したいとき(例: SSDSE-A に無い県を SSDSE-B 基準で洗い出す)は外部結合+WHERE 右.key IS NULL が定番。 → 内部結合 / 外部結合 / テーブル結合
ウィンドウ関数
RANK() / ROW_NUMBER() / LAG() / SUM() OVER (...)。 GROUP BY が行を潰すのに対し、 行を保持したまま 順位・前年差・累積・移動平均を併記できる。 本ページ「ウィンドウ関数 — 都道府県内の順位付け」の実例(PARTITION BY year)を参照。 GROUP BY の上位互換とよく言われる。
サブクエリ / CTE
「平均人口を超える県」のようにクエリ結果を条件に使う のがサブクエリ。 深い入れ子は WITH 名前 AS (...) のCTE でフラット化すると劇的に読みやすくなり、 中間結果に名前を付けて再利用できる。 WITH RECURSIVE なら地域階層のような再帰構造も 1 本で展開可能。
集約と GROUP BY
SUM / AVG / COUNT / MIN / MAX をキー単位で計算する基本操作。 「粒度を粗くする(47 行 → 8 地方)」変換だと捉えると、 どの列がキーでどの列が集約対象かが自然に決まる。 → グループ集計 (groupby)
インデックス / 実行計画
オプティマイザは統計情報を元に実行計画を選ぶ。 EXPLAIN で「Seq Scan(全走査)か Index Scan か」「結合方式(Nested Loop / Hash / Merge)」を読み、 遅いクエリは索引追加やクエリ書き換えで直す。 索引は読取りを速くするが書込みを遅くするトレードオフがある。
NULL 処理
COALESCE(col, 0) で既定値を補い、 NULLIF(a, b) でゼロ除算回避、 集計時は「NULL を 0 とみなすか除外するか」を意識する。 三値論理(罠①)を理解していれば IS DISTINCT FROM など NULL 安全な比較演算子も使いこなせる。
SQL と pandas の対応
WHERE↔df[mask]、 GROUP BY↔groupby().agg()、 JOIN↔merge()、 ORDER BY↔sort_values()、 LIMIT↔head() と 1 対 1 に近い。 大量データの集約は DB 側で、 柔軟な行処理・可視化は pandas 側で、 と役割分担するのが定石。 → pandas
宣言的 vs 手続き的
SQL(宣言的)は「何が欲しいか」だけ書き最適化は任せる ── 短く書けるが実行計画は制御しにくい。 pandas / 手書きループ(手続き的)は「どう処理するか」を自分で決める ── 細かい制御ができる反面、 冗長で最適化も自前。 大きな集合演算は宣言的に、 込み入った逐次処理は手続き的に、 が使い分けの勘所。
🔗 関連ページ
リレーショナルDB ・
RDB 詳細 ・
データベース ・
主キー ・
外部キー ・
内部結合 ・
外部結合 ・
テーブル結合 ・
データ結合 ・
グループ集計 (groupby) ・
pandas
※ 真理値表・多対多結合の 2 表は挙動説明のための架空 例。 その他の数値・列コード(A1101=総人口、 A4101=出生数 ほか)は SSDSE-B-2026 の実データに基づく。