テーブル定義書とは
テーブル定義書(Table Definition Document)は、DBの全テーブルについてカラム名・データ型・桁数・NULL可否・デフォルト値・制約・インデックスを定義した設計書です。ER図が「何と何が関係しているか」を示すのに対し、テーブル定義書は「テーブルがどんなカラムを持つか」を具体的に定義します。
💡 テーブル定義書はDDLに変換できる精度で書く
テーブル定義書の品質目標は「この設計書からSQLのCREATE TABLE文を迷いなく書けること」です。型・桁数・NULL可否・デフォルト値が全て明記されていれば、実装担当者はDDL作成に設計書の解釈を要しません。
① カラム定義(型・桁数・NULL・デフォルト値)
商品マスタテーブル(m_product)のカラム定義例を以下に示します。
| No. | カラム名(物理) | カラム名(論理) | 型 | 桁数/精度 | NULL可 | デフォルト値 | 備考 |
|---|---|---|---|---|---|---|---|
| 1 | product_id | 商品ID | BIGINT | — | 不可 | GENERATED ALWAYS AS IDENTITY | PK・サロゲートキー |
| 2 | product_code | 商品コード | VARCHAR | 20 | 不可 | — | UNIQUE制約。英数字・ハイフンのみ |
| 3 | product_name | 商品名 | VARCHAR | 100 | 不可 | — | — |
| 4 | category_id | カテゴリID | INTEGER | — | 不可 | — | FK: m_category.category_id |
| 5 | unit_price | 単価 | NUMERIC | 10, 2 | 不可 | 0 | 税抜価格。0以上 |
| 6 | tax_type | 税区分 | SMALLINT | — | 不可 | 10 | 10=標準税率, 8=軽減税率, 0=非課税 |
| 7 | release_date | 発売日 | DATE | — | 可 | NULL | 未発売の場合はNULL |
| 8 | description | 商品説明 | TEXT | — | 可 | NULL | 長文テキスト |
| 9 | is_public | 公開フラグ | BOOLEAN | — | 不可 | FALSE | TRUE=公開, FALSE=非公開 |
| 10 | delete_flg | 削除フラグ | BOOLEAN | — | 不可 | FALSE | 論理削除。TRUE=削除済み |
| 11 | created_at | 作成日時 | TIMESTAMP WITH TIME ZONE | — | 不可 | CURRENT_TIMESTAMP | — |
| 12 | created_by | 作成者ID | BIGINT | — | 不可 | — | FK: m_user.user_id |
| 13 | updated_at | 更新日時 | TIMESTAMP WITH TIME ZONE | — | 不可 | CURRENT_TIMESTAMP | 更新時に自動更新(トリガー) |
| 14 | updated_by | 更新者ID | BIGINT | — | 不可 | — | FK: m_user.user_id |
② 制約の定義(PK・FK・UNIQUE・CHECK)
テーブルに設定する全制約を明記します。制約はDDLに直接反映される設計決定事項です。
| 制約名 | 種別 | 対象カラム | 参照先(FK) | 説明 |
|---|---|---|---|---|
pk_m_product | PRIMARY KEY | product_id | — | 商品IDを主キーとする |
uq_m_product_code | UNIQUE | product_code | — | 商品コードは重複不可 |
fk_m_product_category | FOREIGN KEY | category_id | m_category.category_id | カテゴリマスタへの参照整合性 |
fk_m_product_created_by | FOREIGN KEY | created_by | m_user.user_id | 作成者ユーザーへの参照整合性 |
chk_m_product_price | CHECK | unit_price | — | unit_price >= 0 |
chk_m_product_tax | CHECK | tax_type | — | tax_type IN (0, 8, 10) |
③ インデックス設計
クエリのWHERE句・JOIN条件・ORDER BY句で使用するカラムにインデックスを設計します。インデックスは検索性能を向上させますが、INSERT/UPDATE/DELETEのコストが増加するため、必要なものだけに絞ります。
| インデックス名 | テーブル | カラム | 種別 | 作成理由 |
|---|---|---|---|---|
idx_m_product_category_id | m_product | category_id | 通常 | カテゴリ別商品一覧クエリのJOIN高速化 |
idx_m_product_product_code | m_product | product_code | UNIQUE(PK以外) | 商品コードでの高速参照(UNIQUE制約で自動作成) |
idx_m_product_delete_flg | m_product | delete_flg | 通常(部分) | WHERE delete_flg = FALSE の高速化(部分インデックス推奨) |
idx_t_order_customer_id | t_order | customer_id | 通常 | 顧客別受注一覧クエリのJOIN高速化 |
idx_t_order_order_date | t_order | order_date | 通常 | 期間指定受注検索のWHERE高速化 |
④ データ型選択ガイドライン
プロジェクト標準としてのデータ型選択ルールを定義します(PostgreSQL基準)。
| 用途 | 推奨型 | 非推奨 | 理由 |
|---|---|---|---|
| サロゲートキー(PK) | BIGINT GENERATED ALWAYS AS IDENTITY | SERIAL | SERIAL は非推奨(PostgreSQL 10+)。UUIDはインデックス効率が低下するため原則BIGINT |
| 可変長文字列 | VARCHAR(n) | CHAR(n) | CHAR は固定長でパディングが発生。可変長にはVARCHAR使用 |
| 長文テキスト | TEXT | VARCHAR(65535) | PostgreSQLのTEXTは無制限で性能差なし |
| 金額・数量(精度重要) | NUMERIC(p, s) | FLOAT / DOUBLE | 浮動小数点は金融計算で誤差が生じる |
| 日付のみ | DATE | TIMESTAMP | 日付のみの情報にTIMESTAMPを使うと時刻0:00:00が意図せず混入 |
| 日時(タイムゾーン考慮) | TIMESTAMP WITH TIME ZONE | TIMESTAMP | グローバル対応・サマータイム対応が必要な場合はWITH TIME ZONE必須 |
| フラグ・真偽値 | BOOLEAN | SMALLINT(0/1) | BOOLEAN型の方が意図が明確でTRUE/FALSEリテラルが使える |
⑤ 共通カラムの設計
全テーブルに共通して持たせるカラムをプロジェクト標準として定義します。これにより監査証跡・論理削除が一貫して実装されます。
-- テーブル定義書から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からテーブル定義書を自動出力する
"""
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)を使っていないか |