web-dev-qa-db-ja.com

1対「ゼロまたは1」の関係

次の要件があります。

  • すべてのトランザクションには、次のいずれかのタイプがあります。デビット、クレジット、入金、または引き出し。

  • デビットまたはクレジット取引には、リンクされた請求書レコードが必要であり、銀行口座レコードはありません。

  • 預金または引き出し取引には、リンクされた銀行口座レコードがあり、請求書レコードがない必要があります。


現在、私の基本的なデザインは次のとおりです。

Rough Database Design

私の現在の解決策は:

  1. 持ってる aトランザクションテーブルの請求書および銀行口座テーブルへの2つのnull可能な外部キー。ただし、null許容の外部キー列は適切な設計ではないと思います。
  2. トランザクションテーブルにリンクする請求書および銀行口座テーブルに外部キーを設定します。ただし、クエリを行うのは難しいようで、1対多の関係をモデル化します(1対オプション1の関係ではありません)。

私が概説したどのアプローチがより良い解決策ですか?それとも、より良い私の制約を満たす別の方法はありますか?

6

3番目の代替案を提案させてください。triggersを使用してビジネスルールをチェックし、外部キー制約と同じように挿入/更新を失敗させることができます。

あなたのシナリオでは、私はこのようなものをセットアップしました:

CREATE TRIGGER Transaction_BEFORE_INSERT BEFORE INSERT ON Transaction_Table FOR EACH ROW
BEGIN

-- deposit, withdraw must have a bank account and no invoice
if NEW.`Type` in ('deposit','withdraw') then

   if not(select count(*) from Account_Table where id = NEW.id) 
      or (select count(*) from Invoice_Table where id = NEW.id) 
   then
      SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'Error message';
   end if;
-- credit, debit must have an invoice and no bank account
elseif NEW.`Type` in ('credit','debit') then
   if not(select count(*) from Invoice_Table where id = NEW.id) 
      or (select count(*) from Account_Table where id = NEW.id) 
   then
      SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'Another error message';
   end if;
end if;
END

ユーザー定義の例外の一般的な状態であるSQLSTATE 45000を使用しました。これをカスタマイズして、適切なエラーメッセージを設定することもできます。

同様のチェックを行うBEFORE UPDATEトリガーを作成する必要もあります。

この方法でトリガーを悪用すると、パフォーマンスが低下します。各挿入/更新は、他のテーブルで2つの選択を実行します。できるだけ速くなるように選択を最適化する必要があります。トランザクション数の多いデータベースでは、悪い解決策になる可能性があります。したがって、この方法を選択する場合は、本番環境で実行する前に影響をテストしてください。

1
socaire

トランザクションテーブルはなく、[BankAccountTransaction]テーブルと[CreditTransaction]テーブルがあります。

はい、トランザクションを2つのテーブルに分割します。1つは[BankAccount]へのFK、もう1つは[請求書]へのFKです。

必要に応じて、「親」[トランザクション]テーブルを作成し、それを使用して上記の2つのテーブル間の関係をテーブルに仲介することができます。これらの違いはわかりません。または、クエリと同様にレポートすることだけが重要な場合は、UNIONを簡単に使用できるビューを作成できます。

                        +---------------------+                +---------------+
                        |                     |                |               |
        +---------------+  CreditTransaction  +---------------->    Invoice    |
+-------v------+        |                     |                |               |
|              |        +---------------------+                +---------------+
|              |
| Transaction  |
|              |
|              |          +------------------------+       +----------------+
+-------^------+          |                        |       |                |
        |                 | BankAccountTransaction |       | BankAccount    |
        +-----------------+                        +------->                |
                          |                        |       |                |
                          +------------------------+       +----------------+
0
jean

通常、非常に多くのNULL可能列は設計が不適切であることを示していますが、場合によっては、これらの列を用意し、データベースCONSTRAINTSを使用して0-1の関係とビジネスルールが維持されるようにすることもできます。これがあなたの解決策#1です。ソリューション#2はデータベースによって簡単に支援されないため、invoicesbank_accountsの両方のテーブルに対応する行がない場合、最終的に1つのトランザクションが発生する可能性があります。

少しの間、MySQLを使用していないと仮定しましょう1 (少なくとも、バージョン5.7以降)。

実際にチェックを実行する別のデータベースを使用しているとしましょう2、1つのinvoice_id列と1つのbank_account_id列、および適切な行をREFERENCEingすることを保証する必要な制約を含む、次のようなスキーマを使用することは理にかなっています。適切なテーブル(あなたが呼ぶものlinks)とCHECKSは、適切なテーブルが表示され、対応していないものがそこにないことを確認します。

CREATE TYPE transaction_type AS ENUM
    ('debit', 'credit', 'deposit', 'withdraw') ;
-- Note: this could be a table with four values (and probably four ids)    

CREATE TABLE invoices
(
    invoice_id integer /* serial */ PRIMARY KEY,
    other_data text
) ;

CREATE TABLE bank_accounts
(
    bank_account_id integer /* serial */ PRIMARY KEY,
    name text,
    other_data text
) ;

CREATE TABLE transactions
(
    transaction_id integer /* serial */ PRIMARY KEY, 
    type transaction_type,
    nominal decimal(10, 2),
    invoice_id integer REFERENCES invoices(invoice_id) ON UPDATE CASCADE ON DELETE RESTRICT,
    bank_account_id integer REFERENCES bank_accounts(bank_account_id) ON UPDATE CASCADE ON DELETE RESTRICT,

    -- Constraints for your business rules
    CONSTRAINT chk_debit_and_credit_must_have_bank_account 
         CHECK (case when type in ('debit','credit') then 
                    bank_account_id IS NOT NULL 
                else true end),
    CONSTRAINT chk_debit_and_credit_must_not_have_invoice 
         CHECK(case when type in ('debit','credit') then 
                   invoice_id IS  NULL 
                else true end),
    CONSTRAINT chk_deposit_and_withdraw_must_have_invoice 
        CHECK(case when type in ('deposit','withdraw') then 
                  invoice_id IS NOT NULL 
              else true end),
    CONSTRAINT chk_deposit_and_withdraw_must_not_have_bank_account 
        CHECK(case when type in ('deposit','withdraw') then 
                  bank_account_id IS NULL 
              else true end) 
) ;

これを念頭に置いて、次のデータ...

-- Adding two invoices
INSERT INTO invoices
   (invoice_id, other_data)
VALUES
   (1, 'data for invoice 1'),
   (2, 'data for invoice 2')
;

-- Adding two bank accounts
INSERT INTO bank_accounts
   (bank_account_id, other_data)
VALUES
   (1000, 'Bank account 1000'),
   (1001, 'Bank account 1001')
;

... 1つの合法性を持つことができますINSERT

-- Good credit and debit
INSERT INTO transactions 
    (transaction_id, type, nominal, invoice_id, bank_account_id)
VALUES
    (2000, 'credit', 1000.00, NULL, 1000),
    (2001, 'debit',   900.00, NULL, 1000) ;

...およびいくつかの違法なもの(データベースによって拒否されている)

-- Bad credit, it's got invoice
INSERT INTO transactions 
    (transaction_id, type, nominal, invoice_id, bank_account_id)
VALUES
    (2002, 'credit', 1000.00, 1, 1000) ;
エラー:リレーション「トランザクション」の新しい行がチェック制約「chk_debit_and_credit_must_not_have_invoice」に違反しています
詳細:失敗した行には(2002、クレジット、1000.00、1、1000)が含まれています。
-- Bad credit, it's got not bank_account_id
INSERT INTO transactions 
    (transaction_id, type, nominal, invoice_id, bank_account_id)
VALUES
    (2003, 'credit', 1000.00, NULL, NULL) ;
エラー:リレーション「トランザクション」の新しい行はチェック制約「chk_debit_and_credit_must_have_bank_account」に違反しています
詳細:失敗した行には(2003、クレジット、1000.00、null、null)が含まれています。

(および他のすべての組み合わせ)

制約の名前を注意深く選択すると、誤った挿入や更新のデバッグに役立ちます。最大速度が必要な場合、すべての制約を1つのチェック式に減らすことができます。私は通常、データベースが私を助けて、シンプルに保つようにします(1つの式ではなく、4つの簡単な名前と4つの読みやすい式)。

すべての設定はdbfiddle ここにあります


1これが理由です。

残念ながら、 MySQLのCREATE TABLE に関するMySQL 5.7マニュアルから、MySQLはあまり役に立ちません。

[〜#〜]チェック[〜#〜]

CHECK句は解析されますが、すべてのストレージエンジンによって無視されます。セクション1.8.2.3「外部キーの違い」を参照してください。

2私はPostgreSQLを使用しました。いくつかの構文のバリエーションがあれば、これはSQL ServerまたはOracleでも機能します

0
joanolo