テーブル定義書の位置づけ

ER 図がテーブル間の「構造と関係」を示すのに対し、テーブル定義書は各テーブルの「詳細仕様」を定義する補完的なドキュメントだ。実装者がこの定義書だけ見てDDLを書けるレベルの精度が求められる。

⚠️ よくある不足定義

「VARCHAR(255)」のみで桁数の根拠・業務的最大値が不明、コード列の設定可能値が未定義、インデックスの作成根拠が不明、CRUD 情報がなくてどの処理が更新するか追跡できない — これらは後のバグの温床になる。

定義すべき項目一覧

分類定義項目必須度
テーブル基本テーブル物理名・論理名・スキーマ名
テーブル基本テーブル説明・分類・保持期間
テーブル基本想定レコード数(初期・1年後・5年後)
カラム列No・物理名・論理名・データ型・桁数
カラムNULL可否・PK/FK/UK フラグ・デフォルト値
カラム設定可能値(コード値の場合)
カラムカラム説明・備考
制約制約名・種類・対象カラム・参照先(FK)
インデックスインデックス名・対象カラム・種別・作成理由
CRUDこのテーブルを CRUD する処理ID/名称

テーブル基本情報

項目内容
スキーマ名public
テーブル物理名products
テーブル論理名商品マスタ
分類マスタテーブル
テーブル説明販売商品の基本情報・価格・在庫数を管理する。論理削除方式を採用する。
想定レコード数初期: 5,000件 / 1年後: 20,000件 / 5年後: 100,000件
保持期間無期限(論理削除で管理)
パーティションなし

カラム定義

カラム定義表(products テーブル)
No | 物理名          | 論理名       | 型               | 桁  | NULL | PK | FK | UK | デフォルト  | 設定値/説明
---|-----------------|-------------|------------------|-----|------|----|----|----|-----------|-----------------------------------------
1  | product_id      | 商品ID       | BIGINT           | —   | ×    | ○  |    |    | AUTO      | シーケンス自動採番
2  | category_id     | カテゴリID   | BIGINT           | —   | ○    |    | ○  |    | NULL      | FK→categories.category_id
3  | product_code    | 商品コード   | VARCHAR          | 20  | ×    |    |    | ○  | —         | 英数字+ハイフン。自社採番ルール準拠
4  | product_name    | 商品名       | VARCHAR          | 200 | ×    |    |    |    | —         | 販売用の商品名称
5  | description     | 商品説明     | TEXT             | —   | ○    |    |    |    | NULL      | 詳細説明文(HTML不可)
6  | unit_price      | 単価         | NUMERIC          |12,2 | ×    |    |    |    | 0         | 税抜き単価(円)。0以上
7  | stock_quantity  | 在庫数       | INTEGER          | —   | ×    |    |    |    | 0         | 0以上。在庫引当時に排他ロック取得
8  | is_active       | 販売フラグ   | BOOLEAN          | —   | ×    |    |    |    | true      | false=販売停止
9  | created_at      | 作成日時     | TIMESTAMPTZ      | —   | ×    |    |    |    | NOW()     | レコード作成日時
10 | updated_at      | 更新日時     | TIMESTAMPTZ      | —   | ×    |    |    |    | NOW()     | 最終更新日時(トリガーで自動更新)
11 | deleted_at      | 削除日時     | TIMESTAMPTZ      | —   | ○    |    |    |    | NULL      | NULL=有効。論理削除日時

制約定義

制約名種別対象カラム内容
pk_productsPRIMARY KEYproduct_id主キー制約
uk_products_codeUNIQUEproduct_code商品コードの重複禁止
fk_products_catFOREIGN KEYcategory_id → categories.category_idON DELETE SET NULL
chk_products_priceCHECKunit_priceunit_price >= 0
chk_products_stockCHECKstock_quantitystock_quantity >= 0

インデックス定義

インデックス名対象カラム種別作成理由
uk_products_codeproduct_codeUNIQUE商品コード検索(WHERE product_code = ?)
idx_products_categorycategory_id通常カテゴリ別商品一覧取得
idx_products_activeis_active(WHEREで部分)部分 WHERE is_active=true AND deleted_at IS NULL販売中商品の絞り込み高速化

CRUD 参照一覧

処理ID処理名CRUD
API-003商品登録API
API-004商品一覧取得API
API-005商品更新API
API-006商品削除API○(論理)
BTH-002在庫引当バッチ

Python Tips — DDL の自動生成

Python — テーブル定義 dict から DDL を自動生成
"""
テーブル定義辞書から PostgreSQL DDL を自動生成する
"""
TABLE_DEF = {
    "name": "products",
    "columns": [
        {"name":"product_id",     "type":"BIGINT GENERATED ALWAYS AS IDENTITY", "nullable":False, "pk":True},
        {"name":"category_id",    "type":"BIGINT",         "nullable":True},
        {"name":"product_code",   "type":"VARCHAR(20)",    "nullable":False},
        {"name":"product_name",   "type":"VARCHAR(200)",   "nullable":False},
        {"name":"description",    "type":"TEXT",           "nullable":True},
        {"name":"unit_price",     "type":"NUMERIC(12,2)",  "nullable":False, "default":"0"},
        {"name":"stock_quantity", "type":"INTEGER",        "nullable":False, "default":"0"},
        {"name":"is_active",      "type":"BOOLEAN",        "nullable":False, "default":"true"},
        {"name":"created_at",     "type":"TIMESTAMPTZ",    "nullable":False, "default":"NOW()"},
        {"name":"updated_at",     "type":"TIMESTAMPTZ",    "nullable":False, "default":"NOW()"},
        {"name":"deleted_at",     "type":"TIMESTAMPTZ",    "nullable":True},
    ],
    "constraints": [
        "CONSTRAINT uk_products_code UNIQUE (product_code)",
        "CONSTRAINT fk_products_cat FOREIGN KEY (category_id) REFERENCES categories(category_id) ON DELETE SET NULL",
        "CONSTRAINT chk_products_price CHECK (unit_price >= 0)",
        "CONSTRAINT chk_products_stock CHECK (stock_quantity >= 0)",
    ]
}

def gen_ddl(tbl: dict) -> str:
    cols = []
    pk_cols = [c["name"] for c in tbl["columns"] if c.get("pk")]
    for c in tbl["columns"]:
        line = f'  {c["name"]:<20} {c["type"]}'
        if not c["nullable"]:
            line += " NOT NULL"
        if "default" in c:
            line += f' DEFAULT {c["default"]}'
        cols.append(line)
    if pk_cols:
        cols.append(f'  CONSTRAINT pk_{tbl["name"]} PRIMARY KEY ({", ".join(pk_cols)})')
    for con in tbl.get("constraints", []):
        cols.append(f"  {con}")
    return f'CREATE TABLE {tbl["name"]} (
' + ",
".join(cols) + "
);"

print(gen_ddl(TABLE_DEF))

レビューチェックリスト

#チェック項目
1全カラムに論理名・説明が記述されているか
2コード値カラムに設定可能値一覧が記載されているか
3桁数に業務的な根拠があるか(最大値が検討されているか)
4CRUD 一覧が定義されているか
5論理削除方針が定義されているか
6想定レコード数が記載されているか