1

私はオンライン請求プラットフォームを構築していますが、すべての請求書に記載される項目に関連する正規化の概念に苦労しています。私のテーブル構造は次のとおりです。

雇用主

  • ID (PK、自動インクリメント)
  • empID (整数)
  • 名前 (varchar)
  • (その他の無関係なフィールド)

メンバー

  • ID (PK、自動インクリメント)
  • empID (雇用主への外部キー)
  • メンバー ID (int)
  • 名前 (varchar)
  • (その他の無関係なフィールド)

予定

  • planid (PK、自動インクリメント)
  • 説明 (varchar)
  • 価格 (10 進数)

member_plans

  • ID (PK、自動インクリメント)
  • memberid (メンバーへの外部キー)
  • planid (プランへの外部キー、int)
  • status (int、アクティブの場合は 1、非アクティブの場合は 0)
  • 開始 (日時)
  • end (日時、null の可能性あり)

請求書

  • 請求書 ID (PK、自動インクリメント)
  • empID (雇用主への外部キー)
  • 日付 (日時)
  • 小計 (10 進数)
  • バット (10 進数)
  • previous_owed (10 進数)
  • ステータス (整数)

請求書_アイテム

  • ID (PK、自動インクリメント)
  • 請求書 ID (請求書の外部キー)
  • memberid (メンバーへの外部キー)
  • planid (plans の外部キー)
  • 価格 (10 進数)

メンバーは雇用者グループに属しています。雇用主は、会員に代わって会費を支払います。各メンバーは複数のプランを選択できるため、請求書には、各メンバーのサブアイテム (プラン ID、説明、価格) とともに項目 (メンバー ID、名前) が含まれます。各メンバーによるすべてのプランの合計がグループ化され、オンライン レビューのために雇用主に請求されます。

いつでも戻って、選択した日付に基づいて請求書を作成できるようにしたいと考えています。現在、基本的には、既存のすべての member_plans のスナップショットを毎月インポートし、invoice_items にインポートして、invoiceid を割り当てています。これは大量のデータがレプリケートされているように見えます (ただし、 member_plans の開始日と終了日は特定の要因を正確に示しているわけではないため、実際のスナップショットには必要な場合があります。つまり、終了日はいつでも「まるで「メンバーは、チャージバックまたは法的な問題により、特定の期間アクティブではありませんでした。」

Invoice_items テーブルを正規化するための適切で効率的な方法はありますか、それとも私がリストした方法が本当にこれを行う唯一の適切な方法ですか? いずれにせよ 7 か月後に請求書をアーカイブする計画であるため、テーブルのサイズが懸念されますが、終わりのない成長テーブルではありません。

4

1 に答える 1

1

これは、invoice_items に関するいくつかの小さな設計上の問題です。

ID (PK, autoincrement)
invoiceid (FK to invoices)
memberid (FK to members)
planid (FK to plans)
price (decimal)

ID は不要であり、他に変更がなければ、壊れた代理キーです。サロゲートとして自然キーの代わりになるはずですが、自然キーを作成していないため、壊れています。(代理母という言葉は、本質的に「代理母」を意味します。たとえば、代理母は、自然分娩の母親の代わりになります。)

いくつかの疑似 SQL を見てみましょう。

create table invoice_items (
  invoiceid integer not null,

  -- Traditionally, line items are numbered sequentially starting at 1
  -- for each invoice. Your traditions might be different. "Some sane value"
  -- prevents you from sending someone a 10,000 line invoice by mistake.
  -- See below (way below) for implementing CHECK() constraints in MySQL.
  -- A constraint that says, "Start with 1, no gaps" would be nice. I'll
  -- let you code that one. ;)
  line_item_num integer not null check (line_item_num >= 1 and
                                        line_item_num <= [some sane value]),

  memberid integer not null,
  planid integer not null,

  -- Choose the range to fit your application, and to prevent egregious mistakes.
  price decimal(...) not null check (price between -10000 and 10000),

  -- This constraint implements the traditional idea of invoice line items.
  primary key (invoiceid, line_item_num),

  -- This constraint prevents billing a single member plan twice on one invoice.
  -- It might need to be dropped. For example, if you invoice one line item for
  -- the base price for a member plan, then also invoice one line item for 
  -- a discount to the same member plan, you'd need to drop this constraint.
  unique (invoiceid, memberid, planid),

  foreign key (invoiceid) references invoices (invoiceid),

  -- This foreign key needs to reference the single table member_plans, not the 
  -- two tables members and plans. Referencing the single table prevents you from
  -- invoicing a member for a plan that member hasn't chosen.
  foreign key (memberid, planid) references member_plans (memberid, planid)
);

この表の一部として「説明」に言及しましたが、列のリストから除外しました。私も捨てました。

MySQL の制約を確認する

MySQL は CHECK() 制約をサポートしていません。場合によっては、別のテーブルへの外部キー参照として CHECK() 制約を実装することが実用的です。たとえば、上記の line_item_num に対して記述した CHECK() 制約を、行項目番号のテーブルへの外部キー参照として実装すると実用的です。

また、外部キー参照が実用的でない場合もあります。たとえば、価格の範囲が広すぎて、この方法を実装できない場合があります。-10000.00 から +10000.00 の範囲には、数百万行かかります。1 つの代替方法は、トリガーを使用することです。最悪の場合、アプリケーション コードと例外レポートに頼らざるを得なくなる可能性があります。(クラックをすり抜けた無効な値を検索するために、例外レポートが時折実行されます。)

他のいくつかのことを見てください。. .

通常、請求書 ID 番号は自動インクリメント整数ではありません。自動インクリメント整数にはギャップがある場合があります。会計士はギャップを嫌います。遅かれ早かれ、彼らは請求書番号 10156 に何が起こったのかを知りたいと思うようになるでしょ

employees.empID が一意である場合、ID 番号を持つ別の列を追加しても一意性は高くなりません。members.memberid についても同様です。カーゴ カルト プログラミングを参照してください。

于 2012-07-26T11:19:45.350 に答える