-- 価格が1000円以上5000円以下(両端を含む)
SELECT * FROM products
WHERE price BETWEEN 1000 AND 5000;
-- これは以下と同じ意味
SELECT * FROM products
WHERE price >= 1000 AND price <= 5000;
📝 BETWEENのポイント
BETWEENは両端の値を含む(以上・以下)
数値だけでなく、日付にも使える
コードがシンプルになるので、範囲指定には積極的に使いましょう
2-5. IN:複数の値に一致
INを使うと、複数の値のいずれかに一致するデータを取得できます。
-- カテゴリが「食品」「飲料」「お菓子」のいずれかの商品
SELECT * FROM products
WHERE category IN ('食品', '飲料', 'お菓子');
-- これは以下と同じ(INの方がスッキリ)
SELECT * FROM products
WHERE category = '食品'
OR category = '飲料'
OR category = 'お菓子';
✅ INの使いどころ
特定のIDリストに該当するデータを取得したいとき
複数のカテゴリを指定したいとき
ORを何度も書くより読みやすくなる
2-6. LIKE:パターンマッチング
LIKEを使うと、文字列の部分一致でデータを検索できます。
パターン
意味
例
%
0文字以上の任意の文字列
'山%' → 山、山田、山本太郎
_
任意の1文字
'山_' → 山田、山本(2文字のみ)
-- 「山」で始まる名前
SELECT * FROM users WHERE name LIKE '山%';
-- 結果:山田太郎、山本花子、山口次郎
-- 「田」で終わる名前
SELECT * FROM users WHERE name LIKE '%田';
-- 結果:山田、内田、田
-- 「田」を含む名前
SELECT * FROM users WHERE name LIKE '%田%';
-- 結果:山田太郎、田中一郎、内田美咲
-- 3文字の名前
SELECT * FROM users WHERE name LIKE '___';
-- 結果:山田太、佐藤花、鈴木一(3文字のみ)
-- メールアドレスが設定されていないユーザー
SELECT * FROM users
WHERE email IS NULL;
-- メールアドレスが設定されているユーザー
SELECT * FROM users
WHERE email IS NOT NULL;
⚠️ NULLの比較に注意!
= NULLは使えません!必ずIS NULLを使いましょう。
❌ 間違い:WHERE email = NULL(動作しない)
✅ 正しい:WHERE email IS NULL
2-8. 日付の条件指定(ETLで頻出)
ETLでは、日付範囲でデータを絞り込むことが非常に多いです。
-- 2024年1月のデータを取得
SELECT * FROM orders
WHERE order_date >= '2024-01-01'
AND order_date < '2024-02-01';
-- 昨日のデータを取得(PostgreSQL)
SELECT * FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '1 day'
AND order_date < CURRENT_DATE;
-- 過去30日間のデータを取得
SELECT * FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '30 days';
-- 今日のデータを取得
SELECT * FROM orders
WHERE order_date >= CURRENT_DATE
AND order_date < CURRENT_DATE + INTERVAL '1 day';
📝 日付範囲指定のポイント
開始日は「>=」、終了日は「<」を使うのがベストプラクティス
「<」を使うことで、境界の重複を防ぐ
CURRENT_DATEは今日の日付を返す関数
INTERVAL '1 day'は「1日間」を意味する
📊 3. ORDER BY、GROUP BY、LIMIT
3-1. ORDER BY:結果を並び替える
ORDER BYを使うと、結果を指定したカラムで並び替えることができます。
-- 価格の安い順(昇順:ASC)
SELECT * FROM products
ORDER BY price ASC;
-- 価格の高い順(降順:DESC)
SELECT * FROM products
ORDER BY price DESC;
-- 複数のカラムで並び替え(カテゴリ順、その中で価格順)
SELECT * FROM products
ORDER BY category ASC, price DESC;
📝 ORDER BYのポイント
ASC:昇順(小さい順、A→Z、古い順)。省略時のデフォルト
DESC:降順(大きい順、Z→A、新しい順)
複数カラム指定時は、左から順に優先される
3-2. LIMIT:取得件数を制限する
LIMITを使うと、取得するデータの件数を制限できます。
-- 最初の10件だけ取得
SELECT * FROM products
LIMIT 10;
-- 価格が高い順で上位5件を取得
SELECT * FROM products
ORDER BY price DESC
LIMIT 5;
-- 11件目から20件目を取得(ページネーション)
SELECT * FROM products
ORDER BY product_id
LIMIT 10 OFFSET 10; -- 10件スキップして10件取得
✅ LIMITの使いどころ
テスト時:大量データの一部だけを確認したいとき
ページネーション:一覧表示で1ページ分だけ取得
ランキング:上位N件を取得
パフォーマンス:不要なデータを取得しない
3-3. GROUP BY:集計する
GROUP BYを使うと、データをグループ化して集計できます。
-- カテゴリ別の商品数を集計
SELECT
category,
COUNT(*) AS product_count
FROM products
GROUP BY category;
-- カテゴリ別の平均価格を集計
SELECT
category,
AVG(price) AS avg_price,
MAX(price) AS max_price,
MIN(price) AS min_price
FROM products
GROUP BY category;
-- 商品数が10以上のカテゴリだけを表示
SELECT
category,
COUNT(*) AS product_count
FROM products
GROUP BY category
HAVING COUNT(*) >= 10;
📝 WHEREとHAVINGの違い
WHERE:集計前のデータを絞り込む
HAVING:集計後の結果を絞り込む
例:「価格が1000円以上の商品」で「カテゴリ別に集計」して「商品数が5以上のカテゴリ」を表示
-- WHERE(集計前の絞り込み)とHAVING(集計後の絞り込み)の組み合わせ
SELECT
category,
COUNT(*) AS product_count,
AVG(price) AS avg_price
FROM products
WHERE price >= 1000 -- 集計前:1000円以上の商品だけを対象
GROUP BY category
HAVING COUNT(*) >= 5 -- 集計後:商品数5以上のカテゴリだけ表示
ORDER BY avg_price DESC;
-- ordersテーブルにusersとproductsを結合
SELECT
o.order_id,
o.order_date,
u.name AS customer_name,
p.product_name,
p.price,
o.quantity,
(p.price * o.quantity) AS total_amount
FROM orders o
INNER JOIN users u ON o.user_id = u.user_id
INNER JOIN products p ON o.product_id = p.product_id;
# ===== 増分抽出の完成コード =====
import json
import psycopg2
import pandas as pd
from datetime import datetime
# 設定ファイルのパス
LAST_RUN_FILE = 'last_run.json'
def load_last_run_time():
"""前回実行日時を読み込む"""
try:
with open(LAST_RUN_FILE, 'r') as f:
data = json.load(f)
return data.get('last_run_time')
except FileNotFoundError:
return None
def save_current_run_time(run_time):
"""今回の実行日時を保存する"""
with open(LAST_RUN_FILE, 'w') as f:
json.dump({'last_run_time': run_time}, f)
def extract_orders():
"""注文データを増分抽出する"""
# 現在時刻を記録(処理開始時点)
current_run_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 前回実行日時を取得
last_run_time = load_last_run_time()
# DB接続設定
db_config = {
'host': 'localhost',
'database': 'mydb',
'user': 'postgres',
'password': 'password'
}
try:
# DB接続
with psycopg2.connect(**db_config) as conn:
if last_run_time is None:
# 初回:全量抽出
print("【初回実行】全量抽出を実行します")
query = "SELECT * FROM orders ORDER BY order_id"
else:
# 2回目以降:増分抽出
print(f"【増分抽出】{last_run_time} 以降のデータを取得します")
query = f"""
SELECT * FROM orders
WHERE created_at > '{last_run_time}'
ORDER BY order_id
"""
# データ取得
df = pd.read_sql_query(query, conn)
print(f"取得件数: {len(df)}件")
# 成功したら実行日時を保存
save_current_run_time(current_run_time)
return df
except Exception as e:
print(f"エラー: {e}")
return pd.DataFrame()
# 実行
if __name__ == '__main__':
df = extract_orders()
print(df.head())
💡 増分抽出の設計ポイント
created_atカラム:データ作成日時(必須)
updated_atカラム:データ更新日時(UPDATE対応には必要)
最終実行日時の記録:ファイルやDBに保存して次回に使う
初回は全量抽出:2回目以降が増分抽出
定期的に全量抽出:データの整合性確認のため(月1回など)
📝 STEP 4 のまとめ
✅ このステップで学んだこと
SELECT文:データを取得する基本構文、AS で別名、DISTINCT で重複除外
WHERE句:条件で絞り込み(=, >, <, BETWEEN, IN, LIKE, IS NULL)
AND/OR:複数条件の組み合わせ(括弧で優先順位を明示)
ORDER BY:結果の並び替え(ASC/DESC)
GROUP BY:集計(COUNT, SUM, AVG, MAX, MIN)
JOIN:複数テーブルの結合(INNER JOIN, LEFT JOIN)
増分抽出:前回以降のデータだけを効率的に取得
💡 実務でのポイント
1. SELECT * は避ける
必要なカラムだけを明示的に指定しましょう。
2. 日付範囲は「>=」と「<」を使う
境界の重複を防ぐベストプラクティスです。
3. 増分抽出を基本にする
大量データを毎回全量取得すると、パフォーマンスが劣化します。
🎯 次のステップの予告
次のSTEP 5では、「API・Webスクレイピング基礎」を学びます。
REST APIの呼び出し方
JSONレスポンスの処理
ページネーションの対応
Beautiful Soupを使ったWebスクレイピング
データベース以外のデータソースからデータを取得する方法を習得しましょう!
📝 練習問題
問題 1基礎
usersテーブルから、nameとemailだけを取得するSQLを書いてください。
【解答】
SELECT name, email FROM users;
問題 2基礎
productsテーブルから、価格が1000円以上5000円以下のデータを取得してください。
【解答】
-- 方法1:BETWEENを使う
SELECT * FROM products
WHERE price BETWEEN 1000 AND 5000;
-- 方法2:ANDを使う
SELECT * FROM products
WHERE price >= 1000 AND price <= 5000;
SELECT
u.name AS customer_name,
p.product_name,
o.quantity,
(p.price * o.quantity) AS total_amount
FROM orders o
INNER JOIN users u ON o.user_id = u.user_id
INNER JOIN products p ON o.product_id = p.product_id
WHERE o.order_date >= '2024-01-01'
AND o.order_date < '2024-02-01'
ORDER BY total_amount DESC
LIMIT 10;