ふくふくHukuhuku Inc.
EP.10Prep 8分公開: 2026-05-10

結合の前処理:JOIN キー設計と「漏れ」の防止

「JOIN したら行数が 10 倍になった」「LEFT JOIN したら欠損だらけ」── キー設計の失敗が顕在化する瞬間。設計と検証の作法。

#JOIN#キー設計
CO📔 Google Colab で開く(上から順にセルを実行)
シェア

JOIN は事故の温床。本記事では、JOIN キー設計の作法と、行数チェックでの検証パターンを扱います。

JOIN の事前確認 4 点

  1. 1カーディナリティ:1:1 / 1:N / N:N のどれか
  2. 2主キー検証:JOIN キーが各テーブルで unique か
  3. 3NULL の扱い:JOIN キーに NULL があると LEFT JOIN で欠損
  4. 4型の一致:string vs int の混在で JOIN がヒットしない

での自動検証

schema.yml で JOIN 結果を検証
YAML
models:  - name: stg_orders_with_customers    columns:      - name: order_id        tests:          - unique  # 重複防止(N:N JOIN事故検知)          - not_null    tests:      - dbt_utils.equal_rowcount:          compare_model: ref('stg_orders')  # JOIN 前後で行数同じ

SCD Type 2 との関係

Slowly Changing Dimension の Type 2 で valid_from / valid_to を持つテーブルとの JOIN は、`event_date BETWEEN valid_from AND valid_to` で時点合わせが必須。

Python での JOIN 検証

JOIN 前に行数とカーディナリティを検証
Python
import pandas as pd
def validate_join_keys(left: pd.DataFrame, right: pd.DataFrame, on: str) -> dict:    """JOIN 前のヘルスチェック"""    report = {}
    # 1. キーの NULL 率    report["left_key_null_pct"] = left[on].isna().mean()    report["right_key_null_pct"] = right[on].isna().mean()
    # 2. キーの一意性(主キーかどうか)    report["left_unique"] = left[on].is_unique    report["right_unique"] = right[on].is_unique
    # 3. 型が一致するか    report["dtype_match"] = left[on].dtype == right[on].dtype    report["left_dtype"] = str(left[on].dtype)    report["right_dtype"] = str(right[on].dtype)
    # 4. 共通キー数(カーディナリティ)    common = set(left[on].dropna()) & set(right[on].dropna())    report["common_keys"] = len(common)    report["left_only"] = len(set(left[on].dropna()) - common)    report["right_only"] = len(set(right[on].dropna()) - common)
    return report
report = validate_join_keys(orders, customers, "user_id")for k, v in report.items():    print(f"{k:25}: {v}")
assert report["left_unique"] or report["right_unique"], "両方が N:N → 危険"assert report["dtype_match"], f"型不一致: {report['left_dtype']} vs {report['right_dtype']}"assert report["left_key_null_pct"] < 0.01, "left の JOIN キーに NULL 多い"
JOIN 後の検証(行数の予期しない膨張)
Python
import pandas as pd
def safe_join(left: pd.DataFrame, right: pd.DataFrame, on: str, how: str = "left") -> pd.DataFrame:    """JOIN 後に行数チェックする安全 JOIN"""    n_before = len(left)    result = left.merge(right, on=on, how=how)    n_after = len(result)
    # LEFT JOIN なら行数は変わらないはず    if how == "left" and n_after != n_before:        ratio = n_after / n_before        if ratio > 1.5:            raise ValueError(                f"LEFT JOIN で行数が {n_before}{n_after} ({ratio:.2f}x) "                f"→ right テーブルのキーが unique でない可能性"            )    return result
# 使用例merged = safe_join(orders, customers, on="user_id", how="left")print(f"OK: 行数 {len(merged):,}")
SCD Type 2 との時点 JOIN
Python
import pandas as pd
# customers_history: user_id, name, valid_from, valid_to の SCD Type 2# orders: user_id, order_date
def temporal_join(orders, customers_history, key="user_id", date_col="order_date"):    """注文時点での顧客情報を取得"""    merged = orders.merge(customers_history, on=key, how="left")    mask = (        (merged[date_col] >= merged["valid_from"]) &        ((merged["valid_to"].isna()) | (merged[date_col] < merged["valid_to"]))    )    return merged[mask]
result = temporal_join(orders, customers_history)# 各注文に「その時点の顧客名」を付与

ふくふくの進め方

JOIN で結果がおかしくなる」のご相談には、現状診断 → キー設計レビュー → dbt テスト整備を 1 週間で。

次回予告

EP.11 はカテゴリ変数のエンコーディング。One-Hot / Target / Hashing の使い分け。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp