🧚 お知らせ!Beekeeper Studio は高速でモダンなオープンソースのデータベース GUI です ダウンロード
2025年7月24日 著者:Matthew Rathbone

データベースの1対1リレーションシップは、誤解されやすく、十分に活用されていないことがよくあります。最も使われることの少ないリレーションシップの種類ですが、いつどのように正しく実装すべきかを理解すれば、データベース設計、パフォーマンス、保守性を大きく向上させることができます。

この包括的なガイドでは、基本的な概念から高度な実装パターンまで、1対1リレーションシップについて知っておくべきことを、実際の例と実践的な SQL コードとともに解説します。

1対1リレーションシップとは?

1対1(1:1)リレーションシップは、テーブル A の各レコードがテーブル B のちょうど1つのレコードに対応し、その逆も成り立つ場合に存在します。これにより、双方向に機能する対称的な関係が生まれます。

データベースの1対1リレーションシップの図

主な特徴

  • 双方向の一意性:どちら側のレコードにも、対応するレコードがちょうど1つ存在します
  • 任意の参加:リレーションシップを持たないレコードも存在できます
  • 参照整合性:外部キー制約によってデータの一貫性が保たれます
  • 論理的な分離:整理やパフォーマンス上の理由でデータを分割します

概念的な例

人とパスポートの関係を考えてみましょう:

  • 1人が持てる有効なパスポートは1つだけです
  • 各パスポートはちょうど1人に属します
  • パスポートを持たない人もいます(任意)
  • パスポートは人なしでは存在できません

この自然な1:1の関係は、別々のテーブルを使ったデータベースモデリングに最適な題材です。

1対1リレーションシップを使うべき場面

1. パフォーマンスの最適化

列の多い大きなテーブルを分割して、クエリのパフォーマンスを向上させます:

-- Frequently queried user data
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    status VARCHAR(20) DEFAULT 'active'
);

-- Rarely accessed profile data  
CREATE TABLE user_profiles (
    user_id INTEGER UNIQUE NOT NULL,
    bio TEXT,
    preferences JSON,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

メリット:メインテーブルへのクエリの高速化、メモリ使用量の削減、インデックス効率の向上。

2. データの分離

機密情報や任意の情報を分離します:

-- Public employee information
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    employee_number VARCHAR(20) UNIQUE,
    first_name VARCHAR(50),
    department_id INTEGER
);

-- Restricted HR data
CREATE TABLE employee_hr_details (
    employee_id INTEGER UNIQUE NOT NULL,
    salary DECIMAL(10,2),
    social_security_number VARCHAR(11),
    FOREIGN KEY (employee_id) REFERENCES employees(id)
);

3. モジュール型の機能

別テーブルを使って機能の有効化・無効化を行います:

-- Base account functionality
CREATE TABLE accounts (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE,
    email VARCHAR(100) UNIQUE
);

-- Premium features (optional)
CREATE TABLE premium_features (
    account_id INTEGER UNIQUE NOT NULL,
    max_projects INTEGER DEFAULT 100,
    advanced_analytics BOOLEAN DEFAULT true,
    FOREIGN KEY (account_id) REFERENCES accounts(id)
);

実装方法

「拡張」側のテーブルに、一意制約付きの外部キーを置きます:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL
);

CREATE TABLE user_profiles (
    user_id INTEGER UNIQUE NOT NULL,  -- UNIQUE enforces 1:1
    bio TEXT,
    website VARCHAR(255),
    FOREIGN KEY (user_id) REFERENCES users(id)
);

-- Query with relationship
SELECT u.username, p.bio
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id;

ポイント:

  • UNIQUE 制約で1:1の関係を強制します
  • LEFT JOIN でプロフィールのないユーザーも含めます
  • 将来の変更に最も柔軟に対応できる方法です

方法2:主キーの共有

両方のテーブルで同じ主キーの値を使います:

CREATE TABLE user_profiles (
    user_id INTEGER PRIMARY KEY,  -- Same as users.id
    bio TEXT,
    website VARCHAR(255),
    FOREIGN KEY (user_id) REFERENCES users(id)
);

メリット:パフォーマンスがわずかに良く、概念的な関係がより明確です。

デメリット:後でリレーションシップのルールが変わった場合の柔軟性が低くなります。

適切な方法の選び方

ほとんどのケースでは方法1をおすすめします。次の要素のバランスが最も優れているためです:

  • パフォーマンス - 適切なインデックスによる効率的なクエリ
  • 柔軟性 - リレーションシップのルールを変更しやすい
  • 明確さ - テーブル間の依存関係がわかりやすい

方法2は、リレーションシップがビジネスモデルに本質的に組み込まれていて、変更される可能性が低い場合に適しています。

実際の例

例1:ユーザー認証システム

認証データをプロフィール情報から分離します:

-- Core authentication (frequently accessed)
CREATE TABLE user_accounts (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    last_login TIMESTAMP
);

-- Extended profile (occasionally accessed)
CREATE TABLE user_profiles (
    user_id INTEGER UNIQUE NOT NULL,
    display_name VARCHAR(100),
    bio TEXT,
    avatar_url VARCHAR(255),
    privacy_settings JSON,
    FOREIGN KEY (user_id) REFERENCES user_accounts(id)
);

メリット:

  • 高速なログインクエリ - 認証テーブルが小さく高速なまま保たれます
  • メモリ使用量の削減 - プロフィールデータは必要なときだけ読み込まれます
  • 明確な分離 - 認証と表示の関心事を分けられます

例2:EC サイトの商品管理

公開カタログと社内在庫を分離します:

-- Public product catalog
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    sku VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(200) NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    is_active BOOLEAN DEFAULT true
);

-- Internal inventory management
CREATE TABLE product_inventory (
    product_id INTEGER UNIQUE NOT NULL,
    stock_quantity INTEGER DEFAULT 0,
    warehouse_location VARCHAR(100),
    cost_price DECIMAL(10,2),
    reorder_level INTEGER,
    FOREIGN KEY (product_id) REFERENCES products(id)
);

ユースケース:

  • 公開 API - 在庫を公開せずに商品カタログを提供します
  • 社内システム - 商品データと在庫データの両方にアクセスします
  • セキュリティ - 原価や在庫情報へのアクセスを制限します

例3:コンテンツ管理ワークフロー

公開済みコンテンツと編集ワークフローを分離します:

-- Published articles (public)
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    slug VARCHAR(255) UNIQUE NOT NULL,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    published_at TIMESTAMP NOT NULL
);

-- Editorial workflow (internal)
CREATE TABLE article_drafts (
    article_id INTEGER UNIQUE,  -- NULL for unpublished drafts
    title VARCHAR(255),
    content TEXT,
    status VARCHAR(20) DEFAULT 'draft',
    editor_notes TEXT,
    FOREIGN KEY (article_id) REFERENCES articles(id)
);

ワークフロー上のメリット:

  • すっきりした公開インターフェース - 公開済みのコンテンツのみが表示されます
  • 柔軟な編集プロセス - 公開版がなくても下書きを作成できます
  • パフォーマンス - 公開側のクエリが編集データをスキャンしません

パフォーマンスに関する考慮事項

必須のインデックス

1対1リレーションシップでは、外部キー列に必ずインデックスを作成しましょう:

-- Basic foreign key indexes
CREATE INDEX idx_user_profiles_user_id ON user_profiles(user_id);
CREATE INDEX idx_product_inventory_product_id ON product_inventory(product_id);

-- Partial indexes for conditional relationships
CREATE INDEX idx_active_premium_features ON premium_features(account_id) 
WHERE expires_at > CURRENT_TIMESTAMP;

クエリのベストプラクティス

必要な列だけを選択する:テーブルを結合するときは SELECT * を避けましょう。

適切な JOIN の種類を使う:リレーションシップが任意なら LEFT JOIN、必須なら INNER JOIN を使います。

EXISTS と JOIN:データを取得せずにリレーションシップの有無だけを確認する場合は EXISTS を使います。

-- Efficient existence check
SELECT u.username
FROM users u
WHERE EXISTS (
    SELECT 1 FROM user_profiles p 
    WHERE p.user_id = u.id
);

ストレージの最適化

適切なデータ型を選ぶ:汎用的なテキスト型ではなく、具体的な型(DATE、BOOLEAN)を使いましょう。

フィールド長を考慮する:実際の要件に基づいて VARCHAR のサイズを制限しましょう。

複雑なデータには JSON:アクセス頻度が低く複雑なデータは、複数の列ではなく JSON として保存しましょう。

よくある落とし穴と解決策

落とし穴1:UNIQUE 制約の付け忘れ

問題:UNIQUE 制約を付け忘れると、関連レコードが複数作成できてしまいます(1:1ではなく1:多になります)。

解決策:外部キーには必ず UNIQUE を付けましょう:

CREATE TABLE user_profiles (
    user_id INTEGER UNIQUE NOT NULL,  -- UNIQUE enforces 1:1
    bio TEXT,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

落とし穴2:誤った JOIN の種類

問題:INNER JOIN を使うと、リレーションシップのないレコードが除外されます。

解決策:適切な JOIN の種類を使いましょう:

-- Include all users (even without profiles)
SELECT u.username, p.bio
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id;

落とし穴3:不適切なカスケードルール

問題:誤ったカスケード動作により、正当な操作ができなくなります。

解決策:ビジネスロジックに基づいてカスケードルールを選びましょう:

  • CASCADE:親が削除されたら関連レコードも削除します
  • SET NULL:関連レコードは残し、参照を削除します
  • RESTRICT:関連レコードが存在する場合は削除を禁止します

落とし穴4:過度な正規化

問題:密接に関連するデータを不必要に1:1で分割してしまいます。

解決策:明確なメリットがある場合にのみテーブルを分割しましょう:

  • テーブル分割によるパフォーマンス向上
  • データ分離に関するセキュリティ要件
  • データセット間でアクセスパターンが異なる場合
-- Good: Keep frequently accessed data together
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100),
    first_name VARCHAR(50),  -- Keep basic info together
    last_name VARCHAR(50)
);

-- Good: Split large or sensitive data
CREATE TABLE user_extended_profiles (
    user_id INTEGER UNIQUE NOT NULL,
    detailed_bio TEXT,        -- Large field
    salary_expectations DECIMAL -- Sensitive data
);

高度なパターン

条件付きの1対1リレーションシップ

特定の条件下でのみリレーションシップが適用される場合があります:

-- Premium settings only for premium users
CREATE TABLE premium_settings (
    user_id INTEGER UNIQUE NOT NULL,
    max_projects INTEGER DEFAULT 50,
    advanced_features BOOLEAN DEFAULT true,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

-- Enforce business rule
CREATE UNIQUE INDEX idx_premium_settings 
ON premium_settings(user_id) 
WHERE EXISTS (
    SELECT 1 FROM users 
    WHERE users.id = premium_settings.user_id 
    AND account_type = 'premium'
);

ユースケース:フィーチャーフラグ、サブスクリプションのプラン、ロールベースのデータ。

ポリモーフィックな1対1リレーションシップ

1つのテーブルが複数の異なるエンティティ型に関連する場合:

-- Generic address system
CREATE TABLE addresses (
    addressable_type VARCHAR(50) NOT NULL,  -- 'customer', 'supplier'
    addressable_id INTEGER NOT NULL,
    street_address VARCHAR(255),
    city VARCHAR(100),
    UNIQUE(addressable_type, addressable_id)
);

メリット:1つの住所システムを複数のエンティティ型で使え、住所を一貫して管理できます。

時系列の1対1リレーションシップ

リレーションシップの経時的な変化を追跡します:

-- Employee position history
CREATE TABLE employee_positions (
    employee_id INTEGER NOT NULL,
    title VARCHAR(100),
    effective_date DATE NOT NULL,
    end_date DATE,  -- NULL for current
    FOREIGN KEY (employee_id) REFERENCES employees(id)
);

-- Only one current position per employee
CREATE UNIQUE INDEX idx_current_position 
ON employee_positions(employee_id) 
WHERE end_date IS NULL;

用途:役職の変更、サブスクリプション履歴、ステータスの遷移。

アプリケーションコードとの統合

ORM のサポート

最近のほとんどの ORM は、1対1リレーションシップを標準でサポートしています:

Django の例:

class User(models.Model):
    username = models.CharField(max_length=50, unique=True)
    email = models.EmailField(unique=True)

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.TextField(blank=True)

Rails の例:

class User < ApplicationRecord
  has_one :user_profile, dependent: :destroy
end

class UserProfile < ApplicationRecord
  belongs_to :user
end

API 設計パターン

API で1:1リレーションシップを公開する際は、次の方法を検討しましょう:

埋め込みデータ:利便性のため、関連データをレスポンスに直接含めます。

個別のエンドポイント:ペイロードサイズを抑えるため、関連データ専用のエンドポイントを用意します。

任意の展開:クライアントがクエリパラメータ(?expand=profile)で関連データを要求できるようにします。

選択の基準:

  • データサイズとアクセス頻度
  • パフォーマンス要件
  • クライアントに必要な柔軟性

まとめ

データベースの1対1リレーションシップは、データを効率的かつ安全に整理するための強力なツールです。他のリレーションシップの種類ほど一般的ではありませんが、パフォーマンスの最適化、データ分離によるセキュリティ、関心事の明確な分離といった重要なメリットをもたらします。

重要なポイント:

  1. 1:1の関係を強制するため、外部キーには必ず UNIQUE 制約を使う
  2. ビジネスロジックに基づいて適切なカスケードルールを選ぶ
  3. クエリのパフォーマンスを最適化するため外部キー列にインデックスを作成する
  4. リレーションシップのないレコードも含めるため、クエリではLEFT JOIN を使う
  5. 過度に正規化しない - 明確なメリットがある場合にのみテーブルを分割する
  6. テーブルを分割するかどうかはアクセスパターンを考慮して判断する

1:1リレーションシップを使うべき場面:

  • パフォーマンスの最適化が必要な大きなテーブル
  • アクセス制御が必要な機密データ
  • めったにアクセスされない任意のデータ
  • 有効化・無効化できるモジュール型の機能

関連記事:

Beekeeper Studio でこれらのパターンの実装を練習しましょう。テーブルのリレーションシップを可視化し、実データでクエリをテストできます。

Beekeeper Studioは無料のオープンソースのデータベースGUIです

これまで使った中で最高のSQLクエリ&エディタツールです。データベースの管理に必要なものがすべて揃っています。 - ⭐⭐⭐⭐⭐ Mit

Beekeeper Studioは高速で直感的、そして使いやすいツールです。多数のデータベースに対応し、Windows、Mac、Linuxで快適に動作します。

BeekeeperのLinux版は100%フル機能。手抜きも機能の妥協も一切ありません。

Beekeeper Studio 利用者の声

★★★★★
「Beekeeper Studio は、これまでの SQL ワークフローを完全に置き換えてくれました。高速で直感的で、データベース作業がまた楽しくなりました。」
— Alex K.、データベース開発者
★★★★★
「多くのデータベース GUI を試してきましたが、Beekeeper は機能とシンプルさのバランスが完璧です。とにかくちゃんと動きます。」
— Sarah M.、フルスタックエンジニア