テーブル定義書とは

テーブル定義書(Table Definition Document)は、DBの全テーブルについてカラム名・データ型・桁数・NULL可否・デフォルト値・制約・インデックスを定義した設計書です。ER図が「何と何が関係しているか」を示すのに対し、テーブル定義書は「テーブルがどんなカラムを持つか」を具体的に定義します。

💡 テーブル定義書はDDLに変換できる精度で書く

テーブル定義書の品質目標は「この設計書からSQLのCREATE TABLE文を迷いなく書けること」です。型・桁数・NULL可否・デフォルト値が全て明記されていれば、実装担当者はDDL作成に設計書の解釈を要しません。

① カラム定義(型・桁数・NULL・デフォルト値)

商品マスタテーブル(m_product)のカラム定義例を以下に示します。

No.カラム名(物理)カラム名(論理)桁数/精度NULL可デフォルト値備考
1product_id商品IDBIGINT不可GENERATED ALWAYS AS IDENTITYPK・サロゲートキー
2product_code商品コードVARCHAR20不可UNIQUE制約。英数字・ハイフンのみ
3product_name商品名VARCHAR100不可
4category_idカテゴリIDINTEGER不可FK: m_category.category_id
5unit_price単価NUMERIC10, 2不可0税抜価格。0以上
6tax_type税区分SMALLINT不可1010=標準税率, 8=軽減税率, 0=非課税
7release_date発売日DATENULL未発売の場合はNULL
8description商品説明TEXTNULL長文テキスト
9is_public公開フラグBOOLEAN不可FALSETRUE=公開, FALSE=非公開
10delete_flg削除フラグBOOLEAN不可FALSE論理削除。TRUE=削除済み
11created_at作成日時TIMESTAMP WITH TIME ZONE不可CURRENT_TIMESTAMP
12created_by作成者IDBIGINT不可FK: m_user.user_id
13updated_at更新日時TIMESTAMP WITH TIME ZONE不可CURRENT_TIMESTAMP更新時に自動更新(トリガー)
14updated_by更新者IDBIGINT不可FK: m_user.user_id

② 制約の定義(PK・FK・UNIQUE・CHECK)

テーブルに設定する全制約を明記します。制約はDDLに直接反映される設計決定事項です。

制約名種別対象カラム参照先(FK)説明
pk_m_productPRIMARY KEYproduct_id商品IDを主キーとする
uq_m_product_codeUNIQUEproduct_code商品コードは重複不可
fk_m_product_categoryFOREIGN KEYcategory_idm_category.category_idカテゴリマスタへの参照整合性
fk_m_product_created_byFOREIGN KEYcreated_bym_user.user_id作成者ユーザーへの参照整合性
chk_m_product_priceCHECKunit_priceunit_price >= 0
chk_m_product_taxCHECKtax_typetax_type IN (0, 8, 10)

③ インデックス設計

クエリのWHERE句・JOIN条件・ORDER BY句で使用するカラムにインデックスを設計します。インデックスは検索性能を向上させますが、INSERT/UPDATE/DELETEのコストが増加するため、必要なものだけに絞ります。

インデックス名テーブルカラム種別作成理由
idx_m_product_category_idm_productcategory_id通常カテゴリ別商品一覧クエリのJOIN高速化
idx_m_product_product_codem_productproduct_codeUNIQUE(PK以外)商品コードでの高速参照(UNIQUE制約で自動作成)
idx_m_product_delete_flgm_productdelete_flg通常(部分)WHERE delete_flg = FALSE の高速化(部分インデックス推奨)
idx_t_order_customer_idt_ordercustomer_id通常顧客別受注一覧クエリのJOIN高速化
idx_t_order_order_datet_orderorder_date通常期間指定受注検索のWHERE高速化

④ データ型選択ガイドライン

プロジェクト標準としてのデータ型選択ルールを定義します(PostgreSQL基準)。

用途推奨型非推奨理由
サロゲートキー(PK)BIGINT GENERATED ALWAYS AS IDENTITYSERIALSERIAL は非推奨(PostgreSQL 10+)。UUIDはインデックス効率が低下するため原則BIGINT
可変長文字列VARCHAR(n)CHAR(n)CHAR は固定長でパディングが発生。可変長にはVARCHAR使用
長文テキストTEXTVARCHAR(65535)PostgreSQLのTEXTは無制限で性能差なし
金額・数量(精度重要)NUMERIC(p, s)FLOAT / DOUBLE浮動小数点は金融計算で誤差が生じる
日付のみDATETIMESTAMP日付のみの情報にTIMESTAMPを使うと時刻0:00:00が意図せず混入
日時(タイムゾーン考慮)TIMESTAMP WITH TIME ZONETIMESTAMPグローバル対応・サマータイム対応が必要な場合はWITH TIME ZONE必須
フラグ・真偽値BOOLEANSMALLINT(0/1)BOOLEAN型の方が意図が明確でTRUE/FALSEリテラルが使える

⑤ 共通カラムの設計

全テーブルに共通して持たせるカラムをプロジェクト標準として定義します。これにより監査証跡・論理削除が一貫して実装されます。

SQL — 共通カラム付きCREATE TABLE テンプレート
-- テーブル定義書からDDLへの変換例
-- 共通カラムはCREATE TABLE の末尾に必ず追加する

CREATE TABLE m_product (
    -- 主キー(必須)
    product_id   BIGINT GENERATED ALWAYS AS IDENTITY,

    -- 業務カラム
    product_code VARCHAR(20)  NOT NULL,
    product_name VARCHAR(100) NOT NULL,
    category_id  INTEGER      NOT NULL,
    unit_price   NUMERIC(10, 2) NOT NULL DEFAULT 0,
    tax_type     SMALLINT     NOT NULL DEFAULT 10,
    release_date DATE,
    description  TEXT,
    is_public    BOOLEAN      NOT NULL DEFAULT FALSE,

    -- 共通カラム(全テーブル共通)
    delete_flg   BOOLEAN      NOT NULL DEFAULT FALSE,
    created_at   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    created_by   BIGINT       NOT NULL,
    updated_at   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_by   BIGINT       NOT NULL,

    -- 制約
    CONSTRAINT pk_m_product           PRIMARY KEY (product_id),
    CONSTRAINT uq_m_product_code      UNIQUE (product_code),
    CONSTRAINT fk_m_product_category  FOREIGN KEY (category_id) REFERENCES m_category (category_id),
    CONSTRAINT chk_m_product_price    CHECK (unit_price >= 0),
    CONSTRAINT chk_m_product_tax      CHECK (tax_type IN (0, 8, 10))
);

-- インデックス
CREATE INDEX idx_m_product_category_id ON m_product (category_id);
CREATE INDEX idx_m_product_delete_flg  ON m_product (delete_flg) WHERE delete_flg = FALSE;

-- updated_at 自動更新トリガー
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = CURRENT_TIMESTAMP;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_m_product_updated_at
    BEFORE UPDATE ON m_product
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

COMMENT ON TABLE m_product IS '商品マスタ';
COMMENT ON COLUMN m_product.product_code IS '商品コード(英数字・ハイフンのみ)';
COMMENT ON COLUMN m_product.tax_type IS '税区分: 10=標準税率, 8=軽減税率, 0=非課税';

Python Tips — 既存DBからテーブル定義書を自動出力する

Python — PostgreSQL → テーブル定義書Excel自動出力
"""
PostgreSQLの既存テーブルからテーブル定義書(Excel)を自動生成するスクリプト。
pip install psycopg2-binary openpyxl
"""
import psycopg2
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side


DB_CONFIG = {"host":"localhost","port":5432,"dbname":"mydb","user":"myuser","password":"mypassword"}
HEADERS = ["No.", "カラム名(物理)", "カラム名(論理)", "型", "桁数", "NULL可", "デフォルト値", "備考(コメント)"]
HEADER_COLOR = "4472C4"


def get_columns(conn, table_name: str, schema: str = "public") -> list[dict]:
    """テーブルのカラム情報を取得"""
    sql = """
    SELECT
        c.ordinal_position                       AS no,
        c.column_name,
        c.data_type,
        c.character_maximum_length,
        c.numeric_precision,
        c.numeric_scale,
        c.is_nullable,
        c.column_default,
        pg_catalog.col_description(
            (quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))::regclass,
            c.ordinal_position
        ) AS column_comment
    FROM information_schema.columns c
    WHERE c.table_schema = %s AND c.table_name = %s
    ORDER BY c.ordinal_position;
    """
    with conn.cursor() as cur:
        cur.execute(sql, (schema, table_name))
        rows = cur.fetchall()
    return [
        {
            "no": r[0], "column_name": r[1],
            "data_type": r[2],
            "max_length": r[3], "precision": r[4], "scale": r[5],
            "is_nullable": "可" if r[6] == "YES" else "不可",
            "column_default": r[7] or "",
            "comment": r[8] or "",
        }
        for r in rows
    ]


def write_table_sheet(wb: openpyxl.Workbook, table_name: str, columns: list[dict]) -> None:
    ws = wb.create_sheet(title=table_name[:31])  # シート名は31文字制限

    # ヘッダー行
    thin = Side(style='thin', color="AAAAAA")
    border = Border(left=thin, right=thin, top=thin, bottom=thin)

    for col_idx, header in enumerate(HEADERS, 1):
        cell = ws.cell(row=1, column=col_idx, value=header)
        cell.font = Font(bold=True, color="FFFFFF")
        cell.fill = PatternFill("solid", fgColor=HEADER_COLOR)
        cell.alignment = Alignment(horizontal="center")
        cell.border = border

    # データ行
    for row_idx, col in enumerate(columns, 2):
        # 桁数表示
        if col["max_length"]:
            size = str(col["max_length"])
        elif col["precision"] and col["scale"]:
            size = f"{col['precision']},{col['scale']}"
        elif col["precision"]:
            size = str(col["precision"])
        else:
            size = ""

        values = [
            col["no"], col["column_name"], col["comment"],
            col["data_type"], size, col["is_nullable"],
            col["column_default"], ""
        ]
        for col_idx, value in enumerate(values, 1):
            cell = ws.cell(row=row_idx, column=col_idx, value=value)
            cell.border = border

    # 列幅調整
    col_widths = [6, 30, 25, 22, 10, 8, 30, 40]
    for i, w in enumerate(col_widths, 1):
        ws.column_dimensions[openpyxl.utils.get_column_letter(i)].width = w


if __name__ == "__main__":
    TABLES = ["m_product", "m_customer", "t_order", "t_order_detail"]

    conn = psycopg2.connect(**DB_CONFIG)
    wb = openpyxl.Workbook()
    wb.remove(wb.active)  # デフォルトシートを削除

    for table in TABLES:
        columns = get_columns(conn, table)
        write_table_sheet(wb, table, columns)
        print(f"{table}: {len(columns)}カラム")

    conn.close()
    wb.save("table_definition.xlsx")
    print("\ntable_definition.xlsx を生成しました")

定義チェックリスト

チェック項目確認ポイント
□ 全カラムに型・桁数・NULL可否・デフォルト値が定義されているかDDLを迷いなく書ける精度で定義されているか
□ PK・FK・UNIQUE・CHECK制約が全テーブルに定義されているか制約名が命名規則に従っているか
□ インデックスが検索要件に基づいて設計されているかWHERE/JOIN/ORDER BYで使われるカラムにインデックスがあるか
□ 共通カラム(created_at・updated_at・delete_flg)が全テーブルにあるかプロジェクト標準の共通カラムが漏れなく設計されているか
□ 金額カラムにNUMERIC型が使われているかFLOAT/DOUBLEで金額を管理していないか(精度問題回避)
□ タイムゾーン考慮が必要なカラムにTIMESTAMP WITH TIME ZONEが使われているかグローバル対応が必要なシステムでTIMESTAMP(without)を使っていないか