雪岡 高士郎
税率設計から学ぶDB設計、ちゃんと読んでみる
2026年03月14日
要約を生成中...
はじめに
あかねさんの記事「税率設計から学ぶ実務DB設計」を見て、勘がマジで鈍いのでどう自分ごととして持って帰ったらいいのか全然頭でわからなくて何回か読みました。
復習をそのまま書きます。茜さんの記事のなぞり書きでしかないので、個人的なノートだと思ってください。
まず「最悪の設計」から入る
なぜ茜さんの構成が優れているのかを知るためにまず、優れていないものをAIに聞きながら考えました。本当に何も考えずに見切り発車で考えるとこうなる↓
商品テーブル
- ID
- 商品名
- 価格
- 税率(10%とか)
注文履歴テーブル
- ID
注文詳細テーブル(注文の際にどの商品を頼んだのか。1つの注文履歴に対して複数登録可能)
- ID
- 商品履歴ID(外部キー)
- 商品ID(外部キー)これで何が困るかというと、
税率が変わったときに全商品を書き換えないといけない。しかも過去の注文データも書き変わるので「当時の税率」がわからなくなって以下みたいな人が出てくる。
「2年前に8%で買った注文なのに、今見たら10%になってる」
→ これはおかしい。明細が欲しくてAmazonの注文履歴を見に行った時に購入時と情報変わってたら困りますよね。
茜さんの設計
紹介されていたテーブルは4つ。
TAX_RATES
→ 「いつからいつまで何%だったか」の履歴CATEGORY_TAX_CLASSIFICATIONS
→ 「このカテゴリはこの税区分」の対応表ORDER_TAX_LINES
→ 注文時の税率をそのままコピーして保存(カッコよくいうとスナップショット)
→ これがあるから税率が変わっても注文時のデータが維持できる
AREAS
→ 海外対応のための地域一覧(地域ごとに税率が違うので必要)
茜さんの記事にあるDBをAIに図にしてもらいました↓
実際の買い物フローでDBの動きを追う
買う前(商品一覧の表示)
おにぎりの場合
PRODUCT_CATEGORY_IDを参照
→ 食品カテゴリだとわかるCATEGORY_TAX_CLASSIFICATIONSを参照
→ tax_categoryはREDUCED(軽減)だとわかるTAX_RATESを参照
→ effective_until = nullのレコード
→ 税率は8%だとわかるおにぎり100円(商品テーブルがある前提) × 1.08 = 108円とわかる、UIに表示
この時点ではレコードは作られていません。ただ参照して計算するだけ。
買う瞬間(注文確定)
以下を一緒に買ったケースで考えてみます。
おにぎり(カテゴリ:食品・消費税:8%)
ボールペン(カテゴリ:文房具・消費税:10%)
ORDER_TAX_LINESテーブル(注文時の税率)
※1行目がおにぎり、2行目がボールペン
order_id | tax_category | rate | taxable_amount | tax_amount |
|---|---|---|---|---|
1001 | REDUCED | 0.08 | 100 | 8 |
1001 | STANDARD | 0.10 | 200 | 20 |
TAX_RATESテーブルから参照した税率をORDER_TAX_LINES(注文時の税情報を記録するテーブル)に保存する
買った後(履歴確認)
税金に関してはさっきのORDER_TAX_LINESだけ見ればOK。この時TAX_RATESはもう参照しなくて良い。
全体の参照フローの流れ
タイミング | 参照するテーブル | 目的 |
|---|---|---|
買う前 | TAX_RATES | 今の税率でいくらか計算する |
買う瞬間 | TAX_RATES → ORDER_TAX_LINES | 税率をスナップショットとして保存 |
買った後 | ORDER_TAX_LINESのみ | 当時の事実として参照する |
TAX_RATESは「今表示する税額を参照するのに必要な情報」
ORDER_TAX_LINESは「購入した時点での税金情報を参照するのに必要な情報」
疑問
1. product_category_idは不要では?(商品に直接tax_categoryを持たせちゃうのは?)
結論:正規化のために必要
これはシンプルな理由ですね。
商品テーブルに直接tax_categoryを持たせると、食品が1000商品あれば1000件全部tax_category: REDUCEDと書くことになっちゃいます。
↓こんな感じ
id | 商品名 | price | tax_category |
|---|---|---|---|
1 | おにぎり | 100 | REDUCED |
2 | サンドイッチ | 200 | REDUCED |
3 | お茶 | 150 | REDUCED |
... | ...(食品1000件全部) | ... | REDUCED |
これだと同じ情報の繰り返しで正規化できていない状態。
↓茜さんの設計(カテゴリを経由する場合)はこう
⚪︎product(商品)テーブル
※実際は他にもカラムがあると思いますがシンプルにしています
id | 商品名 | price | product_category_id |
|---|---|---|---|
1 | おにぎり | 100 | 1 |
2 | サンドイッチ | 200 | 1 |
3 | お茶 | 150 | 1 |
⚪︎product_category(商品カテゴリ)テーブル
id | カテゴリ名 |
|---|---|
1 | 食品 |
2 | 文房具 |
⚪︎CATEGORY_TAX_CLASSIFICATIONSテーブル
id | product_category_id | tax_category |
|---|---|---|
1 | 食品 | REDUCED |
茜さんの設計だと、商品側が、product_category_id: 1(食品)を辿るだけで、おにぎり・サンドイッチ・お茶すべてがREDUCEDとわかるようになっている。
そのため、商品が1000件あっても CATEGORY_TAX_CLASSIFICATIONS テーブルにある食品カテゴリレコードのtax_categoryカラムを REDUCED or STANDARD に変えるだけで反映できるんです。
僕との比較↓
僕の設計
食品が1000商品あれば1000件全部tax_category: REDUCEDと書き直さないといけない茜さんの設計
食品が1000件あっても書き直すのは食品カテゴリのtax_categoryカラム1件だけ
2の方が圧倒的に無駄がないわけです👍
正規化気持ち良い👍
2. CATEGORY_TAX_CLASSIFICATIONSテーブルのtax_overrideカラムっては不要では?
わざわざ本当は10%だよみたいな上書きをDBで持たなくてもTAX_RATES側で10%と8%のレコードを用意しておいて、会計計算時に10%の方を参照すればできるくねって考えました。
結論:インボイス制度の要件のため必要
まず、僕はお酒のような食品(8%)でありながら10%になるケースを考えました。
⚪︎元の設計(CATEGORY_TAX_CLASSIFICATIONS)
id | product_category_id | area_id | tax_category | tax_override |
|---|---|---|---|---|
1 | 食品 | 101 | REDUCED | false |
2 | 食品(お酒) | 101 | REDUCED | true |
これをみて、税額が計算したいだけなtax_categoryカラム無くても、以下みたいにSTANDARDにしておけば10%って分かるからよくねって思いました(すみません本当)
↓こんな感じで。
⚪︎tax_overrideをなくした場合
id | product_category_id | area_id | tax_category |
|---|---|---|---|
1 | 食品 | 101 | REDUCED |
2 | 食品(お酒) | 101 | STANDARD |
ですが、問題発生。
なんと、国税庁のインボイス(適格請求書)の記載要件として「軽減税率の対象品目である旨」の明記が義務づけられてました。(?)
リンク:適格請求書等の記載事項
つまり、「計算上10%だが、軽減税率対象食品である」という事実をUIに表示しないといけないってことです。(なるほど)
なので、正確に軽減税率対象(REDUCED)であるかどうかをDBに残しておく必要があるわけです。
食品(お酒)カテゴリ
tax_category: REDUCED ← 「軽減税率対象かどうか」を判断するために必要
tax_override: true ← ただし酒類なので実際の税額計算は10%で行う→ tax_overrideカラムはちゃんと必要でした。
→ 最初は何ならtax_categoryカラムも消せるんじゃないかとか超はやとちりしました
感想
ここまでを理解するだけで時間がとっっっっっっってもかかりましたが、テーブル設計の構成自体は純粋に原則(責務分離や正規化)に基づいて作られていました。
今回理解の難易度が上がった一因として、税という技術外の知識が求められた点があります。
なぜこの設計になっているかを読み解く上で、仕様理解が必須でした。
でも実際に現場でDBの設計にアプリの仕様を落とし込まないといけない時は,そもそも技術外の知識がいるってことがあります。
逆に言えば技術街の知識とか経験が力になるってことでもあります(キリッ)
設計の背景にある「なんで」を追ってしまったら最後、税金のことまで調べないといけなく本当に嫌でした。疲れた頭痛え。
あかねさんありがとうございました😇😇😇


私の言葉足らず部分を全部代わりに解説してくれてて神🙏