web-dev-qa-db-ja.com

トランザクション、参照、および複式簿記を実施する方法は? (PG)

複式簿記は

すべてのトランザクションまたはイベントが少なくとも2つの異なる名目元帳勘定を変更する財務会計システムで財務情報を記録するための一連のルール。

アカウントは「借方」または「貸方」にすることができ、すべての貸方の合計は、すべての借方の合計と等しくなければなりません。

これをPostgresデータベースにどのように実装しますか?次のDDLを指定します。

CREATE TABLE accounts(
    account_id serial NOT NULL PRIMARY KEY,
    account_name varchar(64) NOT NULL
);


CREATE TABLE transactions(
    transaction_id serial NOT NULL PRIMARY KEY,
    transaction_date date NOT NULL
);


CREATE TABLE transactions_details(
    id serial8 NOT NULL PRIMARY KEY,
    transaction_id integer NOT NULL 
        REFERENCES transactions (transaction_id)
        ON UPDATE CASCADE
        ON DELETE CASCADE
        DEFERRABLE INITIALLY DEFERRED,
    account_id integer NOT NULL
        REFERENCES accounts (account_id)
        ON UPDATE CASCADE
        ON DELETE RESTRICT
        NOT DEFERRABLE INITIALLY IMMEDIATE,
    amount decimal(19,6) NOT NULL,
    flag varchar(1) NOT NULL CHECK (flag IN ('C','D'))
);

注:システムは単一のトランザクションで複数の口座から借方/貸方記入できるため、transaction_detailsテーブルでは明示的な借方/貸方口座が指定されていません。

このDDLは、次の要件を作成します。データベーストランザクションがtransactions_detailsテーブルでコミットした後、各transaction_id、例

INSERT INTO accounts VALUES (100, 'Accounts receivable');
INSERT INTO accounts VALUES (200, 'Revenue');

INSERT INTO transactions VALUES (1, CURRENT_DATE);

-- The following must succeed
BEGIN;
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 100, '1000'::decimal, 'D');
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 200, '1000'::decimal, 'C');
COMMIT;


-- But this must raise some error
BEGIN;
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 100, '1000'::decimal, 'D');
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 200, '500'::decimal, 'C');
COMMIT;

これをPostgreSQLデータベースに実装することは可能ですか?トリガーの状態を格納する追加のテーブルを指定しない。

8

まず、これはまさに私が尋ねたときに念頭に置いていた質問です サブセット集約のモデリング制約? これは確かに開始する場所です。その質問はこれよりも一般的です。そのため、ここでの私の答えは、実際的なアプローチについてもう少し情報があります。

あなたはおそらくこれをPostgreSQLで宣言的に実行したくないでしょう。唯一の可能な宣言的ソリューションは1NFを破壊するか、非常に複雑なため、これは命令的に実行することを意味します。

LedgerSMB では、この強制を2つの段階で行うと予想されます(どちらも厳密です)。

  1. すべてのジャーナルエントリは、ストアドプロシージャを介して受信されます。これらのストアドプロシージャは、行項目のリストを配列として受け入れ、合計が0に等しいことを確認します。dbのモデルでは、負の数値が借方になり、正の数値が貸方になる単一の金額列があります(最初から、借方として正の数を、貸方として負の数を指定します。これは少し自然なことですが、ここでの理由は不明です)。借方と貸方は、ストレージでマージされ、取得時にプレゼンテーション層で分離されます。これにより、合計の計算がはるかに簡単になります。

  2. テーブルのシステムフィールドに基づいてコミットをチェックする遅延制約トリガーを使用します。これは、特定のトランザクションで入力された行のバランスをとる必要があることを意味しますが、行自体を超えてこれを行うことができます。

5
Chris Travers

別のアプローチは、単一のレコードを構成するのは、金額の振替であるという立場を採用することです。

したがって、次のような構造になります。

create table ... (
  id                integer,
  debit_account_id  not null REFERENCES accounts (account_id),
  credit_account_id not null REFERENCES accounts (account_id),
  amount            numeric not null);

チェック制約により、借方勘定と貸方勘定が異なり、保管する金額が1つだけであることを確認できます。したがって、データモデルが自然に提供する必要のある整合性が保証されます。

私はこのアプローチをうまく採用したシステムで働いてきました。特定のアカウントに対するレコードのクエリの効率は少し低くなりますが、テーブルはよりコンパクトになり、借方のみまたはクレジットのみの方が少し効率的だったため、アカウントのクエリがより効率的になりました。

3
David Aldridge