PostgreSQL: PostgreSQLのテーブル作成と変更

最終更新:2026-08-26

テーブルはデータベースで最も中心的なオブジェクトです。データを格納する「スプレッドシート」であ��、各行がレコード、各カラムがフィールドです。

1. 学習目標


2. ECチームの実話

(1) 課題: 3つのコアテーブルの設計方法

Aliceのチームに新しい要件が来ました。ECシステムに3つのコアテーブル——users(ユーザー)、products(商品)、orders(注文)——を作成することです。要件は以下の通りです。

Aliceは迷いました。制約はどこに置くべきか?データ型はどう選ぶか?外部キーのカスケード削除はどう設定す��か?

(2) 解決策: CREATE TABLE + 制約

PostgreSQLではテーブル作成時にすべての制約を宣言でき、データベースがデータ品質を保証してくれます。

SQL
-- emailの一意性と自動タイムスタンプ付きのユーザーテーブル作��
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    password_hash CHAR(60) NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 価格 > 0 チェック付きの商品テーブル作成
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(200) NOT NULL,
    price DECIMAL(10, 2) CHECK (price > 0),
    stock INTEGER DEFAULT 0 CHECK (stock >= 0),
    attributes JSONB DEFAULT '{}'::jsonb
);

-- 外部キーとステータスチェック付きの注文テーブル作成
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    product_id INTEGER REFERENCES products(id),
    quantity INTEGER NOT NULL CHECK (quantity > 0),
    status VARCHAR(20) DEFAULT 'pending'
        CHECK (status IN ('pending', 'paid', 'shipped', 'delivered', 'cancelled')),
    created_at TIMESTAMPTZ DEFAULT NOW()
);

(3) 成果


3. CREATE TABLE構文

(1) 完全な構文構造

SQL
CREATE TABLE [IF NOT EXISTS] table_name (
    column_name data_type [column_constraint ...],
    ...
    [, table_constraint ...]
);

(2) カラムレベル制約

制約 構文 説明
PRIMARY KEY column_name type PRIMARY KEY 主キー(一意+NOT NULL)
NOT NULL column_name type NOT NULL NULL不可
UNIQUE column_name type UNIQUE 値は一意である必要
DEFAULT column_name type DEFAULT value デフォルト値
CHECK column_name type CHECK (condition) 条件チェック
REFERENCES column_name type REFERENCES table(col) 外部キー参照

(3) テーブルレベル制約

SQL
-- 複合主キー
CREATE TABLE order_items (
    order_id INTEGER,
    product_id INTEGER,
    quantity INTEGER,
    PRIMARY KEY (order_id, product_id)
);

-- カスケード付きの名前付き外部キー
CREATE TABLE comments (
    id SERIAL PRIMARY KEY,
    user_id INTEGER,
    content TEXT,
    CONSTRAINT fk_user
        FOREIGN KEY (user_id)
        REFERENCES users(id)
        ON DELETE SET NULL
);

4. データ型の概要

PostgreSQLは40以上のデータ型をサポートしています。最も一般的なものを以下に示します。

カテゴリ 説明
整数 SMALLINT 2バイト、-32768~32767 年齢、少量
INTEGER (INT) 4バイト、約±21億 一般的な整数(推奨デフォルト)
BIGINT 8バイト、約±9.2兆 大規模ID、金額(セント単位)
自動採番 SERIAL 自動採番INTEGER 主キーID(推奨)
BIGSERIAL 自動採番BIGINT 大規模テーブルの主キー
IDENTITY SQL標準の自動採番(PG 10+) 新規プロジェクトに推奨
浮動小数点 REAL 4バイト、6桁精度 科学計算
DOUBLE PRECISION 8バイト、15桁精度 科学計算
固定小数点 DECIMAL(p,s) 正確な小数 価格、金額(推奨)
NUMERIC(p,s) DECIMALと同▶ 同上
文字列 VARCHAR(n) 可変長、上限あり 名前、email
TEXT 可変長、上限なし 説明、本文
CHAR(n) 固定長 ハッシュ値、コード
真偽値 BOOLEAN true / false / null フラグ
日付 DATE 日付 誕生日
TIMESTAMP 日時(タイムゾーンなし) ローカル時刻
TIMESTAMPTZ 日時+タイムゾーン グローバルアプリ(推奨)
INTERVAL 時間間隔 期間計算
UUID UUID 128ビットUUID 分散ID
JSON JSON テキストJSON 互換性良好
JSONB バイナリJSON(推奨) 高速インデックス付きクエリ
💡 ヒント: PostgreSQLではVARCHARとTEXTにパフォーマンス差はありません(MySQLとは異なります)。ほとんどの場合、VARCHAR(長さ制限付き)またはTEXT(制限なし)を推奨します。


5. 制約の詳細

(1) PRIMARY KEY

100%
graph TB
    PK[PRIMARY KEY] --> U[UNIQUE<br/>重複値不可]
    PK --> NN[NOT NULL<br/>空にできない]
    PK --> IDX[自動インデックス<br/>B-Treeインデックスが自動生成]

▶ サンプル: 単一カラムと複合主キー

SQL
-- 単一カラム主キー(最も一般的)
CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

-- 複合主キー(ジャンクションテーブル用)
CREATE TABLE product_tags (
    product_id INTEGER REFERENCES products(id),
    tag_id INTEGER REFERENCES tags(id),
    PRIMARY KEY (product_id, tag_id)
);

Output:

TEXT 📖 参照専用
CREATE TABLE

(2) FOREIGN KEY

カスケードアクション ON DELETEの動作 ON UPDATEの動作
CASCADE カスケード削除(ユーザー削除→その注文も削除) カスケード更新
SET NULL NULLに設定 NULLに設定
SET DEFAULT デフォルト値に設定 デフォルト値に設定
RESTRICT 削除拒否(デフォルト動作) 更新拒否
NO ACTION RESTRICTと同じ(SQL標準) RESTRICTと同じ

▶ サンプル: 外部キーとカスケードアクション

SQL
-- 注文明細: 注文削除→その全明細も削除(CASCADE)
-- 商品: 商品削除→product_idをNULLに設定(SET NULL)
CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INTEGER REFERENCES orders(id) ON DELETE CASCADE,
    product_id INTEGER REFERENCES products(id) ON DELETE SET NULL,
    quantity INTEGER NOT NULL DEFAULT 1
);

Output:

TEXT 📖 参照専用
CREATE TABLE

(3) CHECK制約

▶ サンプル: CHECK制約によ��データ品質保護

SQL
-- 価格は正の値、割引額は価格を超えない
CREATE TABLE promotions (
    id SERIAL PRIMARY KEY,
    product_id INTEGER REFERENCES products(id),
    discount_price DECIMAL(10, 2) CHECK (discount_price > 0),
    start_date DATE NOT NULL,
    end_date DATE NOT NULL,
    -- PGのCHECKは同じ行の他のカラムを参照可能!
    CHECK (end_date > start_date)
    -- 注意: CHECKはテーブルをまたぐサブクエリを参照できません。テーブル間の検証にはトリガーが必要です
);

Output:

TEXT 📖 参照専用
CREATE TABLE

6. ALTER TABLE

(1) 一般的な変更操作

操作 構文 説明
カラム追加 ALTER TABLE t ADD COLUMN col type カラムを追加
カラム削除 ALTER TABLE t DROP COLUMN col カラムを削除
カラム名変更 ALTER TABLE t RENAME COLUMN old TO new カラム名を変更
型変更 ALTER TABLE t ALTER COLUMN col TYPE new_type データ型を変更
デフォルト設定 ALTER TABLE t ALTER COLUMN col SET DEFAULT val デフォルト値を設定
デフォルト削除 ALTER TABLE t ALTER COLUMN col DROP DEFAULT デフォルト値を削除
NOT NULL設定 ALTER TABLE t ALTER COLUMN col SET NOT NULL NOT NULLに設定
NOT NULL削除 ALTER TABLE t ALTER COLUMN col DROP NOT NULL NULL許可
制約追加 ALTER TABLE t ADD CONSTRAINT name CHECK (...) CHECKを追加
テーブル名変更 ALTER TABLE old_name RENAME TO new_name テーブル名を変更

▶ サンプル: テーブル構造の変更

SQL
-- 新規カラム追加
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- デフォルト値付きで追加
ALTER TABLE users ADD COLUMN is_active BOOLEAN DEFAULT true;

-- カラム型の変更(互換性のない型にはUSINGが必要)
ALTER TABLE users ALTER COLUMN phone TYPE TEXT;

-- カラム名変更
ALTER TABLE users RENAME COLUMN phone TO phone_number;

-- デフォルト値設定
ALTER TABLE users ALTER COLUMN is_active SET DEFAULT true;

-- CHECK制約の追加
ALTER TABLE products ADD CONSTRAINT price_reasonable
    CHECK (price < 100000);

-- カラム削除
ALTER TABLE users DROP COLUMN IF EXISTS phone_number;

-- テーブル名変更
ALTER TABLE users RENAME TO customers;

Output:

TEXT 📖 参照専用
-- SQL statement executed successfully
⚠️ 注意: カラム型の変更はテーブル全体の書き換えが必要な場合があります(例: VARCHAR(50)→VARCHAR(100)は不要ですが、INTEGER→TEXTは必要です)。��規模テーブルではALTER TABLE ... ALTER COLUMN ... TYPE ... USING ...を使用し、USING句で正しく変換してください。


7. DROP TABLE

▶ サンプル: テーブル削除

SQL
-- 安全な削除(テーブルが存在しなくてもエラーにならない)
DROP TABLE IF EXISTS test_table;

-- カスケード削除(ビューなどの依存オブジェクトも��除)
DROP TABLE IF EXISTS users CASCADE;

Output:

TEXT 📖 参照専用
-- SQL statement executed successfully
オプション 説明
IF EXISTS テーブルが存在しなくてもエラーにならない
CASCADE このテーブルに依存するオブジェクト(ビュー、外部キー参照など)も削除
RESTRICT 依存オブジェクトがある場合削除を拒否(デフォルト動作)
🔥 よくあるミス: DROP TABLEは不可逆です!全てのデータが永久に失われます。本番環境では必ず事前にpg_dumpでバックアップを取ってください。


8. psqlでテーブル構造を確認する

コマンド 機能
\dt 全テーブル一覧 \dt
\dt+ テーブル一覧(サイズ情報付き) \dt+
\d tablename テーブル構造表示 \d users
\d+ tablename テーブル構造表��(詳細) \d+ users

▶ サンプル: テーブル構造の確認

BASH
# 現在のデータベースの全テーブル一覧
\dt

# usersテーブルの構造表示
\d users

Output:

TEXT 📖 参照専用
                                        Table "public.users"
   Column    |          Type          | Collation | Nullable |              Default
-------------+------------------------+-----------+----------+-----------------------------------
 id          | integer                |           | not null | nextval('users_id_seq'::regclass)
 email       | character varying(255) |           | not null |
 name        | character varying(100) |           | not null |
 password_hash | character(60)        |           | not null |
 created_at  | timestamp with time zone |         |          | now()
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
    "users_email_key" UNIQUE CONSTRAINT, btree (email)

9. 完全なサンプル: ECコア3テーブル

SQL
-- ============================================
-- 完全なサンプル: ECコアテーブル
-- 適切な制約付きのユーザー、商品、注文テーブル
-- ============================================

-- 1. ユーザーテーブル
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    password_hash CHAR(60) NOT NULL,
    role VARCHAR(20) DEFAULT 'customer'
        CHECK (role IN ('customer', 'admin', 'manager')),
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 2. 商品テーブル
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(200) NOT NULL,
    description TEXT,
    price DECIMAL(10, 2) NOT NULL CHECK (price > 0),
    stock INTEGER DEFAULT 0 CHECK (stock >= 0),
    category VARCHAR(50),
    attributes JSONB DEFAULT '{}'::jsonb,
    is_available BOOLEAN DEFAULT true,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 3. 外部キー付き注文テーブル
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    status VARCHAR(20) DEFAULT 'pending'
        CHECK (status IN ('pending', 'paid', 'shipped', 'delivered', 'cancelled')),
    total_amount DECIMAL(12, 2) DEFAULT 0 CHECK (total_amount >= 0),
    shipping_address TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 4. 注文明細テーブル(ジャンクションテーブル)
CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INTEGER NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
    product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE RESTRICT,
    quantity INTEGER NOT NULL CHECK (quantity > 0),
    unit_price DECIMAL(10, 2) NOT NULL CHECK (unit_price > 0),
    subtotal DECIMAL(12, 2) GENERATED ALWAYS AS (quantity * unit_price) STORED
);

-- 5. サンプルデータ挿入
INSERT INTO users (email, name, password_hash) VALUES
    ('alice@example.com', 'Alice', '$2a$12$dummyhashforalice1234567890abcdefghijklmnopqr'),
    ('bob@example.com', 'Bob', '$2a$12$dummyhashforbob1234567890abcdefghijklmnopqrstuv');

INSERT INTO products (name, price, stock, category, attributes) VALUES
    ('Running Shoes', 89.99, 150, 'Footwear', '{"color": "red", "size": 42}'::jsonb),
    ('Laptop Backpack', 49.99, 300, 'Accessories', '{"color": "black", "material": "nylon"}'::jsonb);

-- 6. テーブル構造の確認
\d users
\d products
\d orders
\d order_items

❓ よくある質問

Q SERIALとIDENTITYの違いは何ですか?どちらを使うべきですか?
A SERIALはPG独自の構文で、内部的にシーケンスを作成しデフォルト値を設定します。IDENTITYはSQL標準構文(PG 10+)で、より厳格な動作をします(OVERRIDING SYSTEM VALUEを使わないと手動で値を挿入できません)。新規プロジェクトではIDENTITYを推奨します。
Q VARCHARとTEXT、どちらを選ぶべきですか?
A PGではVARCHARとTEXTのパフォーマンスは同じです。VARCHAR(n)には長さ制限があり、TEXTにはありません。提案: 明確な長さ制限がある場合(例: email VARCHAR(255))はVARCHAR、制限がない場合(例: 記事本文)はTEXTを使用してください。長さなしのVARCHARは使わないでください。TEXTと同じです。
Q TIMESTAMPとTIMESTAMPTZ、どちらを使うべきですか?
A 常にTIMESTAMPTZ(タイムゾーン付き)を推奨します。UTC時刻を保存し、表示時にクライアントのタイムゾーンに自動変換します。TIMESTAMPはタイムゾーンがなく、クロスタイムゾーンアプリで混乱を招きます。
Q 外部キーのカスケード削除CASCADEは安全ですか?
A CASCADEは開発では便利ですが(ユーザー削除→注文削除→注文明細削除の3段階カスケード)、本番では注意が必要です。誤って1ユーザーを削除すると大量のデータが失われる可能性があります。提案: 重要なビジネステーブルはRESTRICT、ログ/一時テーブルはCASCADEを使用してください。
Q 大規模テーブルでのALTER TABLEはテーブルをロックしますか?
A 単純な操作(デフォルト値付きのADD COLUMN、VARCHAR長の増加)はテーブルをロックしません。ただしデータ型の変更、NOT NULL制約の追加などは全テーブルスキャンが必要��テーブルをロックします。大規模テーブルではCREATE INDEX CONCURRENTLY(後続のレッスンで説明)を使用するか、メンテナンスウィンドウ中に実行してください。
Q テーブルの最大カラム数は?
A PGテーブルは最大1600カラムまで持てます(実際には8KBの行サイズ制限によりこれより少ない場合があります)。ただし50カラムを超える場合は通常設計上の問題です。テーブル分割やJSONBによるスパースフィールドの保存を検討してください。

📖 まとめ


📝 練習問題

  1. 基本(★): categoriesテーブル(id, name, 説明, created_at)を作成し、nameはNOT NULLかつUNIQUEにしてください。3行のテストデータを挿入後、\d categoriesでテーブル構造を確認してください。

  2. 中級(★★): このレッスンで作成したEC 3テーブルを基に、ALTER TABLEを使用してusersテーブルにlast_login_at TIMESTAMPTZカラムとlogin_count INTEGER DEFAULT 0カラムを追加してください。さらにlogin_countが負にならないCHECK制約を追加してください。

  3. チャレンジ(★★★): product_reviewsテーブルを作成してください。id(主キー)、product_id(productsを参照する外部キー)、user_id(usersを参照する外部キー)、rating(1~5、CHECK制約)、タイトル、content、created_at。要件: ユーザー削除時はレビューを保持するがuser_idをNULLに設定、商品削除時はその商品の全レビューをカスケード削除してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%