はじめに
今回は、正規化による更新異常排除の効果や、正規化が不都合を招くケース、そして非正規化による履歴保持や処理高速化について整理します。
本文
正規化による不都合と非正規化
正規化は、データの冗長性や更新異常(挿入・更新・削除時の不整合発生)を防ぐために行います。一般的には最新・整合性のあるデータを確実に扱うために有効です。しかし、最新化が逆に不都合を招くケースもあります。
履歴や古い情報保持のニーズ
ケース1: 履歴を残す必要がある場合
たとえば、顧客マスタは常に最新情報を更新しますが、「過去に顧客がどんな登録情報だったか」という履歴を残したい場合、単純な正規化モデルでは不便です。
サンプル:顧客履歴テーブル
顧客履歴(顧客コード, 顧客名, 適用開始日, 適用終了日, 変更連番)
顧客コード | 顧客名 | 適用開始日 | 適用終了日 | 変更連番 |
---|---|---|---|---|
C001 | 田中一郎 | 2024-01-01 | 2024-06-30 | 1 |
C001 | 田中一郎(改) | 2024-07-01 | NULL | 2 |
ここで、NULL
の適用終了日は最新データを意味します。正規化された1顧客1行構造では過去データが消えてしまいますが、履歴保持用のテーブルを用意することで過去の状態も参照可能です。
ケース2: 売上伝票や受注情報の履歴保持
売上発生時点の単価や商品名を残すには、伝票明細
テーブルに価格や商品名をコピーしておく非正規化が有効です。
サンプル:伝票明細テーブル
伝票明細(伝票番号, 商品コード, 商品名, 単価, 数量)
伝票番号 | 商品コード | 商品名 | 単価 | 数量 |
---|---|---|---|---|
1001 | P001 | りんご | 120 | 10 |
1001 | P002 | みかん | 100 | 5 |
このように、商品
テーブルは常に最新単価を持つ一方、伝票明細
には取引時点の単価・商品名を持たせることで履歴を確保します。
処理高速化のための非正規化
正規化を行うとテーブルが増え、データ取得時に多くの結合が必要になります。これがパフォーマンス低下を招く場合、あえて非正規化して高速化を図ることがあります。
1. 導出属性を持たせる
集計結果や累計額などは、SQLで毎回計算すると負荷が高まります。事前に計算結果を属性として保持しておく方法があります。
サンプル:累計購入額を保持した顧客テーブル
顧客(顧客コード, 顧客名, 購入累計額)
顧客コード | 顧客名 | 購入累計額 |
---|---|---|
C001 | 田中一郎 | 50000 |
C002 | 鈴木花子 | 32000 |
ここで購入累計額
は本来別テーブルから集計できますが、計算済みで保持することで検索を高速化します。変更時にはトリガーやアプリケーション側ロジックで累計額を更新し、整合性を維持します。
2. 属性を重複して持たせる
よく参照される値(例:マイレージ倍率など)を別テーブル参照ではなく、顧客テーブル側にも持たせることで結合回数を減らし、高速化を図ります。
サンプル:マイレージ倍率を重複保持
マイレージサービス(累計下限額, マイレージ倍率)
購入累計下限額 | マイレージ倍率 |
---|---|
20000 | 1.2 |
50000 | 1.5 |
顧客(顧客コード, 顧客名, 購入累計額, マイレージ倍率)
顧客コード | 顧客名 | 購入累計額 | マイレージ倍率 |
---|---|---|---|
C001 | 田中一郎 | 50000 | 1.5 |
C002 | 鈴木花子 | 32000 | 1.2 |
変更時にはマイレージ倍率をトリガーやストアドプロシージャなどで再計算し、顧客テーブルを更新する必要がありますが、検索時の結合が不要になり高速化が可能です。
3. テーブルを1つにまとめる
複数テーブルに正規化している場合でも、アクセス頻度が高く結合コストが問題なら、一部非正規化して1テーブルに集約することもあります。
ただし、極端な非正規化は更新異常のリスクを増やすため、必要最小限の範囲で留めることが重要です。更新漏れを防ぐための定期的な整合性チェックを行うバッチ処理を導入するといった方法が考えられます。
まとめ
正規化はデータの整合性・更新異常防止に効果的ですが、実務では履歴保持や高速化ニーズにより非正規化を検討する場面も多くあります。履歴確保用テーブルや、導出属性・重複属性を持たせる、テーブルをまとめるといった手法で、パフォーマンスや業務要件に応じた柔軟なデータ設計が可能です。