データ辞書とは、システムで使う「語」と「項目」と「型」を1か所で決めておく一覧表です。 用語集で言葉をそろえ、ドメイン定義で型と桁をそろえ、データ項目辞書で項目名をそろえる、という3表の組み合わせで作ると、テーブルが増えても「同じ意味の項目は、同じ名前・同じ型」という状態を保てます。

この記事では、3表のテンプレート(Markdownとタブ区切り)と、架空の受注管理システムで書いた記入例30行を載せます。あわせて、すでにあるデータベースから項目辞書の下書きを取り出すSQLと、辞書とテーブル定義のずれを見つけるPythonスクリプトを、実行結果といっしょに示します。

確認環境:Ubuntu 24.04、PostgreSQL 16.15、MySQL 8.0.46、Python 3.11.15。掲載したSQLとスクリプトの出力は、この環境で実行したものです。

データ辞書の早見表

3つの表の役割

表 決めること 1行の単位 主な列 先に作る順
用語集 言葉の意味と、使う語・使わない語 語(受注、顧客 など) 用語、読み、英語名、定義、使わない同義語 1
ドメイン定義 型・桁・許容値のまとまり 値の種類(金額、日付、区分 など) ドメイン名、型、桁、許容値・書式、例 2
データ項目辞書 項目名とドメインの対応 項目(受注番号、単価 など) 項目名、物理名、ドメイン、説明、使うテーブル 3

項目辞書には型を直接書かず、ドメイン名だけを書きます。 型と桁はドメイン定義の1行にまとまるので、桁を広げるときに直す場所が1か所で済みます。

ドメインの型・桁・許容値の早見表

よく使うドメインと、PostgreSQL・MySQLでの型の例です。桁は例なので、扱う値の最大に合わせて決めます。ここに挙げた型は、両方のデータベースでテーブルを作成できることを確かめています。

ドメイン PostgreSQL MySQL 許容値・書式の例 決めておくこと
金額(整数) numeric(11,0) decimal(11,0) 0〜99,999,999,999 単位(円)、税込か税抜か、負の値を許すか
単価(小数あり) numeric(11,2) decimal(11,2) 小数2桁まで 端数処理(切り捨て・四捨五入)とその時点
数量 integer int 1〜9999 0や負の値(返品)を許すか、単位
率 numeric(5,2) decimal(5,2) 0.00〜100.00 百分率か小数か(10% を 10 と書くか 0.1 と書くか)
日付 date date 暦にある日 時刻を持たないこと、未定のときの表し方
日時 timestamp with time zone datetime(6) 秒未満6桁まで タイムゾーン、秒未満の桁
年月 char(6) char(6) YYYYMM 文字で持つか、月初日の date で持つか
区分 char(1) char(1) 区分値一覧にある値 値と意味の一覧の置き場所
フラグ boolean tinyint(1) 真・偽 NULL を許すか(許さないのが基本)
コード char(6) char(6) 英大文字1字+数字5桁 固定長か、先頭の0を保つか
番号(伝票番号など) char(12) char(12) 接頭辞+年月+連番 体系と桁、欠番を許すか
連番ID bigint bigint 1以上 採番の方式
名称 varchar(60) varchar(60) 60文字以内、改行なし 文字数かバイト数か
備考 varchar(200) varchar(200) 200文字以内、改行可 上限、改行の扱い
メールアドレス varchar(254) varchar(254) 254文字以内 大文字と小文字を区別するか
電話番号 varchar(15) varchar(15) 数字のみ、ハイフンなし ハイフンを保存するか
郵便番号 char(7) char(7) 数字7桁 ハイフンを保存するか

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「データ辞書の作り方|ドメイン定義と項目辞書のテンプレートと記入例」

データ辞書を作る5つの手順

手順 やること できあがるもの
1 画面・帳票・既存テーブルから語と項目名を集める 候補の一覧(重複したままでよい)
2 同じ意味の語を1つに決め、使わない語を並べる 用語集
3 値の種類ごとに型・桁・許容値を決める ドメイン定義
4 項目名にドメインを1つずつ割り当てる データ項目辞書
5 テーブル定義と機械的に突き合わせる ずれの一覧(0件になるまで直す)

手順1は、あとで紹介するSQLで既存テーブルの項目を一覧にすると早く終わります。手順5は、Pythonスクリプトで毎回同じ確認ができます。

用語集のテンプレートと記入例

用語集は、「どの語を使うか」と同じ重さで「どの語を使わないか」を書くのが要点です。「使わない同義語」の列があると、設計書を検索して置き換える対象がはっきりします。

用語 読み 英語名(物理名に使う語) 定義 使わない同義語
受注 じゅちゅう order 顧客から商品の注文を受け、引き受けたこと。1回の受注に明細が1行以上ある 注文、オーダー、発注
顧客 こきゃく customer 商品を販売する相手の法人または個人 得意先、取引先、客先、ユーザー
商品 しょうひん product 販売の対象として商品マスタに登録した品物 品目、製品、アイテム
金額 きんがく amount 単価×数量で求めた税抜の円の値。小数は持たない 価格、代金、料金
区分 くぶん kbn 取りうる値があらかじめ決まっている分類。値と意味は区分値一覧で管理する 種別、タイプ、フラグ

表計算ソフトに貼れるタブ区切りです(glossary.tsv)。

用語  読み  英語名(物理名に使う語)    定義  使わない同義語
受注  じゅちゅう   order   顧客から商品の注文を受け、引き受けたこと。1回の受注に明細が1行以上ある    注文、オーダー、発注
顧客  こきゃく    customer    商品を販売する相手の法人または個人   得意先、取引先、客先、ユーザー
商品  しょうひん   product 販売の対象として商品マスタに登録した品物    品目、製品、アイテム
金額  きんがく    amount  単価×数量で求めた税抜の円の値。小数は持たない 価格、代金、料金
区分  くぶん kbn 取りうる値があらかじめ決まっている分類。値と意味は区分値一覧で管理する 種別、タイプ、フラグ

定義文の書き方

書くこと 例
何を指すか(範囲) 「引き受けたこと」まで含め、見積の段階は含めない
数え方 1回の受注に明細が1行以上ある
単位と精度 税抜、円、小数なし
まぎらわしい語との違い 区分は値が決まった分類、コードはマスタに登録して増えていく値

物理名の末尾にそろえる語

用語集の英語名に、値の種類を表す末尾の語を組み合わせると物理名になります。末尾の語とドメインを対応させておくと、名前を見ただけで型の見当が付きます。

末尾の語 意味 対応するドメイン 例
_code マスタで管理するコード コード customer_code
_no 伝票などの番号、行の順番 番号、連番 order_no、line_no
_name 名称 名称 product_name
_date 日付(時刻なし) 日付 order_date
_at 日時 日時 created_at
_kbn 区分 区分 order_status_kbn
_amount 金額 金額 total_amount
_flg 真偽 フラグ deleted_flg

ドメイン定義のテンプレートと記入例

ドメインは「同じ型・同じ桁・同じ許容値を持つ値の種類」です。受注管理の記入例は次の11行です。

ドメイン名 対象 PostgreSQLの型 MySQLの型 許容値・書式 例
CD_CUSTOMER 顧客コード character(6) char(6) 英大文字1字+数字5桁 C00012
CD_PRODUCT 商品コード character(8) char(8) 英大文字2字+数字6桁 PR000345
NO_ORDER 受注番号 character(12) char(12) J+西暦年月6桁+連番5桁 J20261000001
NAME 名称 character varying(60) varchar(60) 60文字以内。改行なし 青空商事
AMOUNT 金額・単価 numeric(11,0) decimal(11,0) 0以上の整数。単位は円、税抜 128000
QTY 数量 integer int 1〜9999 12
SEQ 行番号・連番 smallint smallint 1〜999。1から順に振る 3
DATE 日付 date date 暦にある日。時刻は持たない 2026-10-05
DATETIME 日時 timestamp with time zone datetime(6) 登録・更新の時点。日本時間で表示 2026-10-05 09:30:00
KBN 区分 character(1) char(1) 区分値一覧にある1文字 1
NOTE 備考 character varying(200) varchar(200) 200文字以内。改行可 午前中に届ける

型の列は、あとでスクリプトが照合に使うので、データベースが返す書き方(PostgreSQLなら character varying(60)、MySQLなら varchar(60))にそろえて書きます。

ドメイン名   対象  PostgreSQLの型    MySQLの型 許容値・書式  例
CD_CUSTOMER 顧客コード   character(6)    char(6) 英大文字1字+数字5桁 C00012
CD_PRODUCT  商品コード   character(8)    char(8) 英大文字2字+数字6桁 PR000345
NO_ORDER    受注番号    character(12)   char(12)    J+西暦年月6桁+連番5桁   J20261000001
NAME    名称  character varying(60)   varchar(60) 60文字以内。改行なし 青空商事
AMOUNT  金額・単価   numeric(11,0)   decimal(11,0)   0以上の整数。単位は円、税抜  128000
QTY 数量  integer int 1〜9999  12
SEQ 行番号・連番  smallint    smallint    1〜999。1から順に振る   3
DATE    日付  date    date    暦にある日。時刻は持たない   2026-10-05
DATETIME    日時  timestamp with time zone    datetime(6) 登録・更新の時点。日本時間で表示    2026-10-05 09:30:00
KBN 区分  character(1)    char(1) 区分値一覧にある1文字 1
NOTE    備考  character varying(200)  varchar(200)    200文字以内。改行可 午前中に届ける

桁の決め方:型が受け付ける範囲を確かめる

numeric(11,0) は整数11桁までです。PostgreSQLで境界の値を入れると次のようになります。

CREATE TABLE domain_check (amount numeric(11,0), rate numeric(5,2), kbn char(1), d date);
INSERT INTO domain_check (amount) VALUES (99999999999);
INSERT INTO domain_check (amount) VALUES (100000000000);
INSERT INTO domain_check (rate)   VALUES (1000);
INSERT INTO domain_check (kbn)    VALUES ('12');
INSERT INTO domain_check (d)      VALUES ('2026-02-30');
INSERT 0 1
ERROR:  numeric field overflow
DETAIL:  A field with precision 11, scale 0 must round to an absolute value less than 10^11.
ERROR:  numeric field overflow
DETAIL:  A field with precision 5, scale 2 must round to an absolute value less than 10^3.
ERROR:  value too long for type character(1)
ERROR:  date/time field value out of range: "2026-02-30"

型と桁で防げるのは「桁あふれ」と「暦にない日」までです。 numeric(5,2) は 999.99 まで入るので、率を 0〜100 に収めたい、金額を0以上にしたい、といった許容値は、CHECK制約か入力チェックで別に守ります。MySQL 8.0.46 でも、decimal(11,0) の列に 100000000000 を入れると次のエラーになりました。

ERROR 1264 (22003) at line 7: Out of range value for column 'amount' at row 1

PostgreSQLではドメインをそのまま型にできる

PostgreSQLには CREATE DOMAIN があり、ドメイン定義の1行をデータベースの型として登録できます。許容値もいっしょに持たせられます。

CREATE DOMAIN d_amount AS numeric(11,0) CHECK (VALUE >= 0);
CREATE DOMAIN d_kbn    AS char(1);

CREATE TABLE t_dom (price d_amount);
INSERT INTO t_dom VALUES (-1);
ERROR:  value for domain d_amount violates check constraint "d_amount_check"

MySQL 8.0.46 で同じ文を実行すると構文エラーになります。MySQLでは、ドメイン定義の表を正として、列ごとに同じ型を書きます。

ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DOMAIN d_amount AS decimal(11,0)' at line 1

データ項目辞書のテンプレートと記入例

項目辞書は、項目名(日本語)・物理名・ドメインの3つを1行で結び付ける表です。同じ物理名は辞書に1行だけ置き、どのテーブルでもその行に従います。

項目名 物理名 ドメイン 説明 使うテーブル
顧客コード customer_code CD_CUSTOMER 顧客を1件に決めるコード customers, orders
顧客名 customer_name NAME 顧客の正式名称 customers
商品コード product_code CD_PRODUCT 商品を1件に決めるコード products, order_details
商品名 product_name NAME 商品の表示名 products
単価 unit_price AMOUNT 商品1個あたりの税抜の金額 products, order_details
受注番号 order_no NO_ORDER 受注を1件に決める番号 orders, order_details
受注日 order_date DATE 受注を引き受けた日 orders
受注状態区分 order_status_kbn KBN 1:受付 2:出荷済 9:取消 orders
受注金額合計 total_amount AMOUNT 明細の金額の合計 orders
備考 note NOTE 担当者が自由に書く補足 orders, order_details
行番号 line_no SEQ 受注の中での明細の順番 order_details
数量 quantity QTY 受注した個数 order_details
金額 amount AMOUNT 単価×数量 order_details
登録日時 created_at DATETIME 行を登録した日時 全テーブル
項目名 物理名 ドメイン    説明  使うテーブル
顧客コード   customer_code   CD_CUSTOMER 顧客を1件に決めるコード    customers, orders
顧客名 customer_name   NAME    顧客の正式名称 customers
商品コード   product_code    CD_PRODUCT  商品を1件に決めるコード    products, order_details
商品名 product_name    NAME    商品の表示名  products
単価  unit_price  AMOUNT  商品1個あたりの税抜の金額   products, order_details
受注番号    order_no    NO_ORDER    受注を1件に決める番号 orders, order_details
受注日 order_date  DATE    受注を引き受けた日   orders
受注状態区分  order_status_kbn    KBN 1:受付 2:出荷済 9:取消 orders
受注金額合計  total_amount    AMOUNT  明細の金額の合計    orders
備考  note    NOTE    担当者が自由に書く補足 orders, order_details
行番号 line_no SEQ 受注の中での明細の順番 order_details
数量  quantity    QTY 受注した個数  order_details
金額  amount  AMOUNT  単価×数量   order_details
登録日時    created_at  DATETIME    行を登録した日時    全テーブル

用語集5行、ドメイン定義11行、項目辞書14行で、あわせて30行です。この3つのタブ区切りファイル(glossary.tsv、domains.tsv、items.tsv)を、後半のスクリプトがそのまま読み込みます。

項目辞書に書くもの・書かないもの

内容 項目辞書 置き場所
項目名、物理名、ドメイン、意味 書く 項目辞書
型、桁、許容値 書かない ドメイン定義
NOT NULL、主キー、外部キー、索引 書かない テーブル定義書
区分の値と意味の全件 説明に要約だけ 区分値一覧
画面での必須・初期値 書かない 画面項目定義書

題材のテーブルを作る(PostgreSQL)

辞書とテーブル定義の突き合わせを試すために、受注管理の4テーブルを作ります。明細テーブルには、辞書と合わない列をわざと4つ入れてあります(コメントの付いた行)。

CREATE DOMAIN d_amount AS numeric(11,0) CHECK (VALUE >= 0);
CREATE DOMAIN d_kbn    AS char(1);

CREATE TABLE customers (
  customer_code char(6)      PRIMARY KEY,
  customer_name varchar(60)  NOT NULL,
  created_at    timestamptz  NOT NULL DEFAULT now()
);
CREATE TABLE products (
  product_code  char(8)      PRIMARY KEY,
  product_name  varchar(60)  NOT NULL,
  unit_price    d_amount     NOT NULL,
  created_at    timestamptz  NOT NULL DEFAULT now()
);
CREATE TABLE orders (
  order_no         char(12)    PRIMARY KEY,
  order_date       date        NOT NULL,
  customer_code    char(6)     NOT NULL REFERENCES customers,
  order_status_kbn d_kbn       NOT NULL DEFAULT '1',
  total_amount     d_amount    NOT NULL,
  note             varchar(200),
  created_at       timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE order_details (
  order_no     char(12)      NOT NULL REFERENCES orders,
  line_no      smallint      NOT NULL,
  product_code varchar(10)   NOT NULL,          -- products では char(8)
  quantity     integer       NOT NULL,
  unit_price   numeric(9,2)  NOT NULL,          -- products では numeric(11,0)
  amount       d_amount      NOT NULL,
  biko         varchar(100),                    -- orders では note
  created_at   timestamp     NOT NULL DEFAULT now(),  -- 他の表は timestamptz
  PRIMARY KEY (order_no, line_no)
);
COMMENT ON COLUMN orders.order_no IS '受注番号';
COMMENT ON COLUMN orders.total_amount IS '受注金額合計';

information_schemaから項目辞書の下書きを出すSQL(PostgreSQL)

information_schema.columns から、項目名・ドメイン・型・NULL可否・初期値を取り出します。項目名の順に並べるのがこのSQLの要点で、同じ名前の項目がテーブルをまたいで縦に並ぶので、型のずれが目で追えます。

SELECT c.table_name,
       c.column_name,
       coalesce(c.domain_name, '')              AS domain,
       CASE
         WHEN c.data_type IN ('character', 'character varying')
           THEN c.data_type || '(' || c.character_maximum_length || ')'
         WHEN c.data_type = 'numeric'
           THEN 'numeric(' || c.numeric_precision || ',' || c.numeric_scale || ')'
         ELSE c.data_type
       END                                       AS data_type,
       c.is_nullable                             AS nullable,
       coalesce(c.column_default, '')            AS col_default,
       coalesce(col_description(
         format('%I.%I', c.table_schema, c.table_name)::regclass,
         c.ordinal_position), '')                AS comment
FROM information_schema.columns c
JOIN information_schema.tables  t
  ON t.table_schema = c.table_schema AND t.table_name = c.table_name
WHERE c.table_schema = 'public'
  AND t.table_type = 'BASE TABLE'
ORDER BY c.column_name, c.table_name;
  table_name   |   column_name    |  domain  |          data_type          | nullable | col_default |   comment    
---------------+------------------+----------+-----------------------------+----------+-------------+--------------
 order_details | amount           | d_amount | numeric(11,0)               | NO       |             | 
 order_details | biko             |          | character varying(100)      | YES      |             | 
 customers     | created_at       |          | timestamp with time zone    | NO       | now()       | 
 order_details | created_at       |          | timestamp without time zone | NO       | now()       | 
 orders        | created_at       |          | timestamp with time zone    | NO       | now()       | 
 products      | created_at       |          | timestamp with time zone    | NO       | now()       | 
 customers     | customer_code    |          | character(6)                | NO       |             | 
 orders        | customer_code    |          | character(6)                | NO       |             | 
 customers     | customer_name    |          | character varying(60)       | NO       |             | 
 order_details | line_no          |          | smallint                    | NO       |             | 
 orders        | note             |          | character varying(200)      | YES      |             | 
 orders        | order_date       |          | date                        | NO       |             | 
 order_details | order_no         |          | character(12)               | NO       |             | 
 orders        | order_no         |          | character(12)               | NO       |             | 受注番号
 orders        | order_status_kbn | d_kbn    | character(1)                | NO       | '1'::bpchar | 
 order_details | product_code     |          | character varying(10)       | NO       |             | 
 products      | product_code     |          | character(8)                | NO       |             | 
 products      | product_name     |          | character varying(60)       | NO       |             | 
 order_details | quantity         |          | integer                     | NO       |             | 
 orders        | total_amount     | d_amount | numeric(11,0)               | NO       |             | 受注金額合計
 order_details | unit_price       |          | numeric(9,2)                | NO       |             | 
 products      | unit_price       | d_amount | numeric(11,0)               | NO       |             | 
(22 rows)

読み取れることは次のとおりです。

  • created_at、product_code、unit_price は、テーブルによって型が違います。
  • biko は1テーブルにだけあり、同じ意味の note と名前が分かれています。
  • ドメインを使った列は、domain 列にドメイン名、data_type 列にもとの型が出ます。PostgreSQL公式ドキュメントの columns ビューの説明にも、ドメインにもとづく列の data_type はドメインのもとの型を指し、ドメインは domain_name で示すと書かれています。

スクリプトに渡すファイルは、psql の整形なし(-A)・見出しなし(-t)・タブ区切りで、テーブル名・項目名・型の3列だけを書き出します。上のSQLを draft_pg.sql に保存した場合の例です。

psql -d appdb -At -F $'\t' -f draft_pg.sql | cut -f1,2,4 > columns_pg.tsv

(データベース名は appdb に置き換えて載せています。)

information_schemaから項目辞書の下書きを出すSQL(MySQL)

MySQLでは、桁まで含んだ型が COLUMN_TYPE に、列のコメントが COLUMN_COMMENT に入っているので、SQLが短くなります。題材のテーブルは次の定義で作りました(PostgreSQL版と同じ4か所をずらしてあります)。

CREATE TABLE customers (
  customer_code char(6)      PRIMARY KEY COMMENT '顧客コード',
  customer_name varchar(60)  NOT NULL    COMMENT '顧客名',
  created_at    datetime(6)  NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '登録日時'
);
CREATE TABLE products (
  product_code  char(8)       PRIMARY KEY COMMENT '商品コード',
  product_name  varchar(60)   NOT NULL    COMMENT '商品名',
  unit_price    decimal(11,0) NOT NULL    COMMENT '単価',
  created_at    datetime(6)   NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '登録日時'
);
CREATE TABLE orders (
  order_no         char(12)      PRIMARY KEY COMMENT '受注番号',
  order_date       date          NOT NULL    COMMENT '受注日',
  customer_code    char(6)       NOT NULL    COMMENT '顧客コード',
  order_status_kbn char(1)       NOT NULL DEFAULT '1' COMMENT '受注状態区分',
  total_amount     decimal(11,0) NOT NULL    COMMENT '受注金額合計',
  note             varchar(200)              COMMENT '備考',
  created_at       datetime(6)   NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '登録日時',
  FOREIGN KEY (customer_code) REFERENCES customers (customer_code)
);
CREATE TABLE order_details (
  order_no     char(12)      NOT NULL COMMENT '受注番号',
  line_no      smallint      NOT NULL COMMENT '行番号',
  product_code varchar(10)   NOT NULL COMMENT '商品コード',
  quantity     int           NOT NULL COMMENT '数量',
  unit_price   decimal(9,2)  NOT NULL COMMENT '単価',
  amount       decimal(11,0) NOT NULL COMMENT '金額',
  biko         varchar(100)           COMMENT '備考',
  created_at   datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '登録日時',
  PRIMARY KEY (order_no, line_no),
  FOREIGN KEY (order_no) REFERENCES orders (order_no)
);
SELECT c.TABLE_NAME     AS table_name,
       c.COLUMN_NAME    AS column_name,
       c.COLUMN_TYPE    AS data_type,
       c.IS_NULLABLE    AS nullable,
       coalesce(c.COLUMN_DEFAULT, '') AS col_default,
       c.COLUMN_COMMENT AS comment
FROM information_schema.COLUMNS c
JOIN information_schema.TABLES  t
  ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME
WHERE c.TABLE_SCHEMA = DATABASE()
  AND t.TABLE_TYPE = 'BASE TABLE'
ORDER BY c.COLUMN_NAME, c.TABLE_NAME;
+---------------+------------------+---------------+----------+----------------------+--------------------+
| table_name    | column_name      | data_type     | nullable | col_default          | comment            |
+---------------+------------------+---------------+----------+----------------------+--------------------+
| order_details | amount           | decimal(11,0) | NO       |                      | 金額               |
| order_details | biko             | varchar(100)  | YES      |                      | 備考               |
| customers     | created_at       | datetime(6)   | NO       | CURRENT_TIMESTAMP(6) | 登録日時           |
| order_details | created_at       | datetime      | NO       | CURRENT_TIMESTAMP    | 登録日時           |
| orders        | created_at       | datetime(6)   | NO       | CURRENT_TIMESTAMP(6) | 登録日時           |
| products      | created_at       | datetime(6)   | NO       | CURRENT_TIMESTAMP(6) | 登録日時           |
…(省略)…
| order_details | product_code     | varchar(10)   | NO       |                      | 商品コード         |
| products      | product_code     | char(8)       | NO       |                      | 商品コード         |
…(省略)…
| order_details | unit_price       | decimal(9,2)  | NO       |                      | 単価               |
| products      | unit_price       | decimal(11,0) | NO       |                      | 単価               |
+---------------+------------------+---------------+----------+----------------------+--------------------+

(全22行のうち一部を載せています。罫線の位置は見やすいようにそろえています。)

コメントを項目名として使えるので、MySQLでは列コメントに項目辞書の項目名を入れておくと、下書きがそのまま辞書の形に近づきます。 スクリプト用のファイルは次のように書き出します。

mysql appdb -N -B < draft_my.sql | cut -f1-3 > columns_my.tsv

2つのデータベースで使う列の対応

取り出すもの PostgreSQL(information_schema.columns) MySQL(information_schema.COLUMNS)
項目名 column_name COLUMN_NAME
型の名前 data_type DATA_TYPE
桁まで含む型 文字は character_maximum_length、数値は numeric_precision と numeric_scale を組み合わせる COLUMN_TYPE
NULL可否 is_nullable IS_NULLABLE
初期値 column_default COLUMN_DEFAULT
ドメイン名 domain_name (ドメインの仕組みがない)
コメント col_description 関数で取得 COLUMN_COMMENT

辞書とテーブル定義のずれを見つけるPythonスクリプト

項目辞書(items.tsv)、ドメイン定義(domains.tsv)と、SQLで書き出した列の一覧を突き合わせます。標準ライブラリだけで動きます。確認するのは次の4点です。

  1. 同じ名前で型が違う項目
  2. 辞書にない項目名
  3. 辞書のドメインと型が違う項目
  4. 辞書にあるがどのテーブルにも無い項目
"""項目辞書と実際のテーブル定義を突き合わせる。

使い方: python3 check_dict.py columns_pg.tsv pg
        python3 check_dict.py columns_my.tsv my
columns_*.tsv は「テーブル名<TAB>項目名<TAB>型」の3列(見出しなし)。
"""
import csv
import sys
from collections import defaultdict


def read_tsv(path, header=True):
    with open(path, encoding="utf-8", newline="") as f:
        rows = list(csv.reader(f, delimiter="\t"))
    return rows[1:] if header else rows


def main(columns_path, db):
    type_col = {"pg": 2, "my": 3}[db]          # domains.tsv の型の列
    domain_type = {r[0]: r[type_col] for r in read_tsv("domains.tsv")}
    dict_domain = {r[1]: r[2] for r in read_tsv("items.tsv")}   # 物理名 -> ドメイン

    used = defaultdict(list)                   # 項目名 -> [(テーブル, 型)]
    for table, column, data_type in read_tsv(columns_path, header=False):
        used[column].append((table, data_type))

    problems = 0

    print("== 1. 同じ名前で型が違う項目")
    for column, places in sorted(used.items()):
        if len({t for _, t in places}) > 1:
            problems += 1
            print(f"  {column}")
            for table, data_type in places:
                print(f"    {table:<14} {data_type}")

    print("== 2. 辞書にない項目名")
    for column, places in sorted(used.items()):
        if column not in dict_domain:
            problems += 1
            tables = ", ".join(t for t, _ in places)
            print(f"  {column:<14} ({tables})")

    print("== 3. 辞書のドメインと型が違う項目")
    for column, places in sorted(used.items()):
        if column not in dict_domain:
            continue
        domain = dict_domain[column]
        expected = domain_type[domain]
        for table, data_type in places:
            if data_type != expected:
                problems += 1
                print(f"  {table}.{column}: {data_type}"
                      f"  <- 辞書は {domain} = {expected}")

    print("== 4. 辞書にあるがどのテーブルにも無い項目")
    unused = sorted(set(dict_domain) - set(used))
    print("  " + (", ".join(unused) if unused else "(なし)"))

    print(f"-- 項目 {sum(len(v) for v in used.values())} 列 / "
          f"名前 {len(used)} 種類 / 指摘 {problems} 件")
    return 1 if problems else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1], sys.argv[2]))

実行結果(PostgreSQL)

python3 check_dict.py columns_pg.tsv pg; echo "exit=$?"
== 1. 同じ名前で型が違う項目
  created_at
    customers      timestamp with time zone
    order_details  timestamp without time zone
    orders         timestamp with time zone
    products       timestamp with time zone
  product_code
    order_details  character varying(10)
    products       character(8)
  unit_price
    order_details  numeric(9,2)
    products       numeric(11,0)
== 2. 辞書にない項目名
  biko           (order_details)
== 3. 辞書のドメインと型が違う項目
  order_details.created_at: timestamp without time zone  <- 辞書は DATETIME = timestamp with time zone
  order_details.product_code: character varying(10)  <- 辞書は CD_PRODUCT = character(8)
  order_details.unit_price: numeric(9,2)  <- 辞書は AMOUNT = numeric(11,0)
== 4. 辞書にあるがどのテーブルにも無い項目
  (なし)
-- 項目 22 列 / 名前 15 種類 / 指摘 7 件
exit=1

わざと入れた4か所がすべて見つかりました。1は辞書がなくても出せる確認、2と3は辞書があってはじめて出せる確認です。指摘があると終了コードが1になるので、テーブル定義を変更するたびに自動で実行する仕組みに組み込めます。

実行結果(MySQL)

python3 check_dict.py columns_my.tsv my; echo "exit=$?"
== 1. 同じ名前で型が違う項目
  created_at
    customers      datetime(6)
    order_details  datetime
    orders         datetime(6)
    products       datetime(6)
  product_code
    order_details  varchar(10)
    products       char(8)
  unit_price
    order_details  decimal(9,2)
    products       decimal(11,0)
== 2. 辞書にない項目名
  biko           (order_details)
== 3. 辞書のドメインと型が違う項目
  order_details.created_at: datetime  <- 辞書は DATETIME = datetime(6)
  order_details.product_code: varchar(10)  <- 辞書は CD_PRODUCT = char(8)
  order_details.unit_price: decimal(9,2)  <- 辞書は AMOUNT = decimal(11,0)
== 4. 辞書にあるがどのテーブルにも無い項目
  (なし)
-- 項目 22 列 / 名前 15 種類 / 指摘 7 件
exit=1

テーブルを辞書に合わせて直す

PostgreSQLで、明細テーブルを辞書に合わせます。biko は note に名前を変え、桁もドメイン NOTE に合わせます。

ALTER TABLE order_details ALTER COLUMN product_code TYPE char(8);
ALTER TABLE order_details ALTER COLUMN unit_price   TYPE d_amount;
ALTER TABLE order_details ALTER COLUMN created_at   TYPE timestamptz;
ALTER TABLE order_details RENAME COLUMN biko TO note;
ALTER TABLE order_details ALTER COLUMN note TYPE varchar(200);

列の一覧を書き出し直して、もう一度実行した結果です。

== 1. 同じ名前で型が違う項目
== 2. 辞書にない項目名
== 3. 辞書のドメインと型が違う項目
== 4. 辞書にあるがどのテーブルにも無い項目
  (なし)
-- 項目 22 列 / 名前 14 種類 / 指摘 0 件
exit=0

名前の種類が15から14に減り、指摘が0件になりました。データが入っているテーブルで型を変えるときは、既存の値が新しい型に収まるかを先に確かめてから実行します。

指摘が出たときの直し方の判断表

指摘 よくある原因 直し方
同じ名前で型が違う 桁をテーブルごとに決めた ドメインの桁にそろえる。必要な桁のほうが大きければドメイン定義を広げる
同じ名前で型が違う(意味も違う) 別の意味の項目に同じ名前を付けた 名前を分けて、項目辞書に2行で登録する
辞書にない項目名 同義語やローマ字で名付けた(biko と note) 辞書の物理名に変える
辞書にない項目名(新しい項目) 辞書への登録がまだ 用語集とドメインを確かめてから項目辞書に1行足す
ドメインと型が違う テーブル側が先に変わった どちらを正とするか決め、辞書かテーブルの片方を直す
辞書にあるが使われていない 項目を削除した、これから使う 不要なら辞書から外す。予定の項目なら説明に書いておく

データ辞書のチェックリスト

確認すること 見るところ
用語集に「使わない同義語」が書いてある 用語集
定義に、範囲・単位・数え方が入っている 用語集
ドメインごとに型・桁・許容値・例がそろっている ドメイン定義
金額は単位と税の扱い、日時はタイムゾーンが決まっている ドメイン定義
項目辞書の物理名に重複がない 項目辞書
項目辞書の全行にドメインが入っている 項目辞書
物理名の末尾の語とドメインが対応している 項目辞書
スクリプトの指摘が0件 突き合わせの結果

よくある質問

Q. データ辞書とテーブル定義書はどう違いますか? A. テーブル定義書は「このテーブルにどの列があるか」をテーブルごとに書きます。データ辞書は「この項目名はどの型で、どういう意味か」をテーブルをまたいで1回だけ書きます。項目辞書を先に決め、テーブル定義書はそこから項目を選んで並べる、という順にすると名前と型がそろいます。

Q. すでに動いているシステムでも作れますか? A. 作れます。この記事のSQLで項目の一覧を出し、項目名の順に並べて同じ名前をまとめれば、それが項目辞書の下書きになります。型が分かれている項目は、まず辞書に「正とする型」を書き、テーブルの変更は影響の小さいものから順に進めると進めやすくなります。

Q. ドメインはどのくらい細かく分けますか? A. 「型・桁・許容値のどれかが違えば別のドメイン」が目安です。顧客コードと商品コードは桁と書式が違うので分け、顧客名と商品名は同じ60文字なので NAME にまとめました。細かく分けすぎると割り当てに迷うので、まず10〜20個から始めて、必要になったときに分けます。

Q. 表計算ソフトとテキストファイルのどちらで管理しますか? A. 記入と一覧は表計算ソフトが向き、差分の確認と自動チェックはタブ区切りやCSVのテキストが向きます。この記事のように、表計算ソフトで編集してタブ区切りで保存し、スクリプトに読ませる形にすると両方の利点を使えます。

まとめ

  • データ辞書は、用語集・ドメイン定義・データ項目辞書の3表で作ると、語・型・項目名を別々にそろえられます。
  • 用語集には「使わない同義語」を書くと、名前のゆれを検索して直せます。
  • 型と桁はドメイン定義にだけ書き、項目辞書はドメイン名を指す形にすると、桁の変更が1か所で済みます。
  • information_schema を項目名の順に並べると、テーブルをまたいだ型のずれが見えます。PostgreSQLは桁を組み立て、MySQLは COLUMN_TYPE をそのまま使います。
  • Pythonスクリプトで、同じ名前で型が違う項目・辞書にない項目名・ドメインと型のずれを検出でき、題材では7件の指摘を0件まで直せました。
  • PostgreSQLは CREATE DOMAIN でドメインを型として登録でき、許容値も持たせられます。

参考・出典

  • PostgreSQL 16 Documentation「columns」(information_schema) https://www.postgresql.org/docs/16/infoschema-columns.html
  • MySQL 8.0 Reference Manual「The INFORMATION_SCHEMA COLUMNS Table」 https://dev.mysql.com/doc/refman/8.0/en/information-schema-columns-table.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「データ辞書の作り方|ドメイン定義と項目辞書のテンプレートと記入例」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?